Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plans to support Next.js 13 - /app directory #2928

Open
fcisio opened this issue Oct 25, 2022 · 161 comments
Open

Plans to support Next.js 13 - /app directory #2928

fcisio opened this issue Oct 25, 2022 · 161 comments

Comments

@fcisio
Copy link

fcisio commented Oct 25, 2022

The problem

Next JS just release their v13 publicly.
As seen in their docs, emotion has not yet added support.

Is there any plan to add support in the near future?

Thanks.

@adamrneary
Copy link

It might be possible see about circling the wagons with the MUI and Vercel teams, as well, on this. MUI is very widely used (and teams are paying!). I am not sure what the specific numbers are, but I have to imagine we have a very large contingent of MUI/Emotion users overlapping with Next.js. Having these two titans not work together is a miss.

(I suspect if getting this working needed some sponsorship, $$$ could be found!)

@lachlanjc
Copy link

lachlanjc commented Oct 26, 2022

We’re also super looking for this so Theme UI can support the app directory!

@emmatown
Copy link
Member

emmatown commented Oct 27, 2022

We may want to add an explicit API for this but this works today:

// app/emotion.tsx
"use client";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import { useServerInsertedHTML } from "next/navigation";
import { useState } from "react";

export default function RootStyleRegistry({
  children,
}: {
  children: JSX.Element;
}) {
  const [cache] = useState(() => {
    const cache = createCache({ key: "css" });
    cache.compat = true;
    return cache;
  });

  useServerInsertedHTML(() => {
    return (
      <style
        data-emotion={`${cache.key} ${Object.keys(cache.inserted).join(" ")}`}
        dangerouslySetInnerHTML={{
          __html: Object.values(cache.inserted).join(" "),
        }}
      />
    );
  });

  return <CacheProvider value={cache}>{children}</CacheProvider>;
}

// app/layout.tsx
import RootStyleRegistry from "./emotion";

export default function RootLayout({ children }: { children: JSX.Element }) {
  return (
    <html>
      <head></head>
      <body>
        <RootStyleRegistry>{children}</RootStyleRegistry>
      </body>
    </html>
  );
}

// app/page.tsx
/** @jsxImportSource @emotion/react */
"use client";

export default function Page() {
  return <div css={{ color: "green" }}>something</div>;
}

@karlhorky
Copy link

karlhorky commented Oct 27, 2022

@mitchellhamilton did you get this working for you?

Trying in a StackBlitz just now, it seems like it's giving me an error about React.createContext not being a function:

StackBlitz: https://stackblitz.com/edit/vercel-next-js-mxnxa7?file=app%2Fpage.tsx,app%2Flayout.tsx,app%2FEmotionRootStyleRegistry.tsx,next.config.js

event - compiled client and server successfully in 59 ms (403 modules)
error - (sc_server)/node_modules/@emotion/react/dist/emotion-element-b63ca7c6.cjs.dev.js (20:47) @ eval
error - TypeError: React.createContext is not a function
    at eval (webpack-internal:///(sc_server)/./node_modules/@emotion/react/dist/emotion-element-b63ca7c6.cjs.dev.js:19:49)
    at (sc_server)/./node_modules/@emotion/react/dist/emotion-element-b63ca7c6.cjs.dev.js (/home/projects/vercel-next-js-mxnxa7/.next/server/app/page.js:501:1)
    at __webpack_require__ (/home/projects/vercel-next-js-mxnxa7/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(sc_server)/./node_modules/@emotion/react/jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.cjs.dev.js:7:22)
    at (sc_server)/./node_modules/@emotion/react/jsx-dev-runtime/dist/emotion-react-jsx-dev-runtime.cjs.dev.js (/home/projects/vercel-next-js-mxnxa7/.next/server/app/page.js:512:1)
    at __webpack_require__ (/home/projects/vercel-next-js-mxnxa7/.next/server/webpack-runtime.js:33:43)
    at eval (webpack-internal:///(sc_server)/./app/layout.tsx:5:88)
    at (sc_server)/./app/layout.tsx (/home/projects/vercel-next-js-mxnxa7/.next/server/app/page.js:403:1)
    at __webpack_require__ (/home/projects/vercel-next-js-mxnxa7/.next/server/webpack-runtime.js:33:43)
    at Object.layout (webpack-internal:///(sc_server)/./node_modules/next/dist/build/webpack/loaders/next-app-loader.js?name=app%2Fpage&appPaths=%2Fpage&pagePath=private-next-app-dir%2Fpage.tsx&appDir=%2Fhome%2Fprojects%2Fvercel-next-js-mxnxa7%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2Fhome%2Fprojects%2Fvercel-next-js-mxnxa7&isDev=true&tsconfigPath=tsconfig.json!:22:99) {
  type: 'TypeError',
  page: '/'
}
null

Screenshot 2022-10-27 at 19 46 47


It does seem to work if the layout component is made into a client component, but this would be unfortunate:

StackBlitz: https://stackblitz.com/edit/vercel-next-js-tbkg4a?file=app%2Flayout.tsx,app%2Fpage.tsx,app%2FEmotionRootStyleRegistry.tsx,next.config.js

@karlhorky
Copy link

karlhorky commented Oct 27, 2022

Oh it seems like my optimization of removing the /** @jsxImportSource @emotion/react */ and using { compiler: { emotion: true } } (the SWC Emotion transform plugin) caused this to break (I guess this is still using context under the hood, will open an issue in Next.js repo).

Working StackBlitz, based on @emmatown's original example:

StackBlitz: https://stackblitz.com/edit/vercel-next-js-ajvkxp?file=app%2Fpage.tsx,app%2Flayout.tsx,app%2FEmotionRootStyleRegistry.tsx,next.config.js

@karlhorky
Copy link

Reported a bug about the SWC Emotion transform plugin here:

@Andarist
Copy link
Member

Andarist commented Oct 27, 2022

Just note that the presented solution works with the app directory - it still doesn't quite work with streaming. It's not exactly Emotion's fault though, other libraries won't work either because the callback provided to useServerInsertedHTML gets only called once. So it's only possible to inject styles contained in the initial "shell" this way.

You can observe this on this stackblitz that uses Styled Components. I've prepared it by copy-pasting the example from the Next.js docs, on top of that I've just added a single Suspense boundary to "chunk" the stream. The rendered span should have a red text but the whole thing stays green.

@lachlanjc
Copy link

Thank you all so much for the samples & explanation! @Andarist, could you give a little more color on the long-term situation here? If Next resolves the SWC bug & Emotion does an update, where will that leave us with server component support? Are there aspects that are never going to work?

@Andarist
Copy link
Member

To the best of my understanding - we should be able to support server components in the future. Some parts of that are fuzzy to me though. Mainly I'm not sure how to avoid injecting the same styles back to the client on server component refetches. We rely on a list of inserted IDs but server components render for a particular request - where we don't have access to the IDs inserted by previous requests (or by the client, for that matter).

@murrowblue22
Copy link

I too need Mui + emotions to work, this would greatly speed my migration to client/server component architecture

@songhobby
Copy link

As of right now, I converted all the components into client components to ‘migrate’ to nexjs13. 😂 Need this before any meaningful migration

@Rafcin
Copy link

Rafcin commented Nov 8, 2022

@mitchellhamilton Is cache.compat something exclusive to Emotion 10?
When I run this setup on the latest version I get TypeError: Cannot read properties of undefined (reading 'registered') and TypeError: Cannot read properties of undefined (reading 'compat')

@godfrednanaowusu
Copy link

godfrednanaowusu commented Nov 17, 2022

To the best of my understanding - we should be able to support server components in the future. Some parts of that are fuzzy to me though. Mainly I'm not sure how to avoid injecting the same styles back to the client on server component refetches. We rely on a list of inserted IDs but server components render for a particular request - where we don't have access to the IDs inserted by previous requests (or by the client, for that matter).

Have you found any solutions yet? if you have can we kindly get a timeline for emotion-js working with nextjs13

@minervabot
Copy link

minervabot commented Nov 18, 2022

@godfrednanaowusu I ported a next 12 project to next 13 and have not had any trouble with emotion and mui working correctly (besides a breaking change in next/link). The issues here appear to be about using the /app directory instead of /pages. Since /app is listed as beta anyways, perhaps this isn't such a major obstacle to using next 13 with a plan to migrate project structure at a later date.

Essentially by asking to use the react 18 features with /app, this is asking emotion to fully support react 18 fully, which is going to involve some pretty big structural changes to do right.

@unfernandito
Copy link

@minervabot read the title, the issue is about nextjs 13 app directory, not just upgrading and use pages folder

@minervabot
Copy link

@unfernandito I noticed a lot of people were talking about needing this to use 13, which seemed to perhaps deserve some clarification, since /app is not even in the stable next.js docs yet.

@Andarist
Copy link
Member

After talking with the Next.js team and helping them recognize the problem with useServerInsertedHTML and Suspense that issue has been fixed in vercel/next.js#42293

With that fix Emotion roughly works in the /app if you do this:

"use client";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import { useServerInsertedHTML } from "next/navigation";
import { useState } from "react";

export default function RootStyleRegistry({
  children,
}: {
  children: JSX.Element;
}) {
  const [{ cache, flush }] = useState(() => {
    const cache = createCache({ key: "my" });
    cache.compat = true;
    const prevInsert = cache.insert;
    let inserted: string[] = [];
    cache.insert = (...args) => {
      const serialized = args[1];
      if (cache.inserted[serialized.name] === undefined) {
        inserted.push(serialized.name);
      }
      return prevInsert(...args);
    };
    const flush = () => {
      const prevInserted = inserted;
      inserted = [];
      return prevInserted;
    };
    return { cache, flush };
  });

  useServerInsertedHTML(() => {
    const names = flush();
    if (names.length === 0) return null;
    let styles = "";
    for (const name of names) {
      styles += cache.inserted[name];
    }
    return (
      <style
        data-emotion={`${cache.key} ${names.join(" ")}`}
        dangerouslySetInnerHTML={{
          __html: styles,
        }}
      />
    );
  });

  return <CacheProvider value={cache}>{children}</CacheProvider>;
}

(kudos to the @emmatown for providing this implementation)

We need to figure out the exact APIs for handling this within Emotion but conceptually the thing would end up being very similar - you just won't have to override cache.insert in the future and we'll provide this new kind of the flush in Emotion itself.

Note that you should only use Emotion with the so-called client components (we might add "use client"; directive to our files to make this obvious). They can render on the server just fine - for the initial SSRed/streamed HTML but server "refetches" won't render them on the server, they might be rendered on the client when the "refetch" response comes back. This is not Emotion's limitation - it's a limitation for all CSS-in-JS libs like Emotion (including Styled Components, styled-jsx and more)

@stunaz
Copy link

stunaz commented Dec 6, 2023

@dcruzb @schoenwaldnils that's probably because you are not leveraging server components.... so all your components just work as if your were using the pages directory

@quangnmwork
Copy link

@dcruzb @schoenwaldnils that's probably because you are not leveraging server components.... so all your components just work as if your were using the pages directory

So let summary, here is how I dealing with this isssue.

First in tsconfig.json try to put "jsxImportSource": "@emotion/react" in compilerOptions. This will ensure in every file you using emotion it will inject jsxImportSource": "@emotion/react in your file.

Next if you are using Server component put /* @jsxImportSource react */ in the top of the file.

It's a workaround for my project now , hope there some solution better.

@51L3N7-X
Copy link

Still not working with version 14?

@LuisGilGB
Copy link

LuisGilGB commented Dec 15, 2023

For those trying to combine Next JS, Material UI and custom styles (with Tailwind, CSS Modules, etc.), MUI has released the v5.15.0 version with a new package for the integration with Next JS: @mui/material-nextjs

I was trying the previous integration instructions for the App Router (Next JS v14.0.1) and, although most things worked fine, I was having issues with the prepending of MUI styles for the production builds (basically prepend wasn't working for production builds even using @layer, the main workaround that exists for Emotion right now, so MUI styles overlapped my custom ones with Tailwind). After updating MUI to v5.15.0 and using the new package, everything turned fine for both development and production builds. Also, my _app.tsx and root layout.tsx files (still adopting App Router incrementally) got cleaner with the new utilities than I previously had with custom Emotion cache setups.

I guess that the source code of the package may have precious insights for other scenarios trying to combine Next JS and Emotion without Material UI as well.

@rtrembecky
Copy link

Anybody from MUI or community working on an updated version of the next/mui example? https://github.com/mui/material-ui/tree/master/examples/material-ui-nextjs-ts
We still need a separate client flle (with "use client") where ThemeProvider/CssBaseline is defined, right?

@tom-streller-tutti
Copy link

tom-streller-tutti commented Jan 12, 2024

This appears still to be an issue with NextJS 14.0.2 and Emotion v11.

Reading through the comments above and trying a minimally viable project to verify, I concluded that the following is necessary to get emotion or mui working with Next.js app router:

  • Use the provided <AppRouterCacheProvider/> from @mui/material-nextjs - an implementation of the caching provider provided in earlier comments.
  • Add a "use client" at the top of every page.tsx, converting them into client components from the start, disabling any SSR features like database access.
    OR
  • Add the pragma /* @jsxImportSource react */ on the top of the page.tsx and move any component that uses styling or @mui components into a child component that has "use client";. Any component that uses styled components or uses an @mui/material component need to also have "use client"; at the top.

However, all React Server Components only work if they have the pragma /* @jsxImportSource react */ at the top. This includes layout.tsx. And any component that uses this pragma cannot directly use components styled with emotion.

The pragma tells Babel/TypeScript/SWC to use the default react JSX constructor to transpile jsx statements into valid JavaScript. If you have a compiler: { emotion: true }, then by default jsxImportSource": "@emotion/react" is set for the transpilation. Not using the compiler option in the next.config.js inverts the issue, i.e. all your client components now have to have the pragma /* @jsxImportSource "@emotion/react" */ at the top. You'll need to see whichever has less impact on your project.

The emotion JSX constructor appears to generate code that uses createContext without adding a "use client"; to that, which then makes Next.js reject the file. Hence, you must tell the transpiler to use the default react JSX constructor for React Server Components.

This might be fine for a small project, a new green field project, as it's just something to keep in mind. The issue we are facing is that we want to migrate a large codebase to Next.js 14's app/ router. Since the generated error does not provide where the offending RSC is missing the pragma, we would need to spend a lot of time hunting down the hundreds of candidates we have.

My suggestions for fixing this issue with @emotion/react would either be

a) Add the required "use client"; to the generated code when transpiling. (If this is working as intended I cannot verify)
b) Have emotion's JSX transpiler, babel plugin or swc plugin try to detect if the component is an RSC and not generate code that uses createContext for that component.

I do not know how feasible either of these solutions are, as I'm unfamiliar with how Emotion works internally.

My only other tip for whoever ends up here to read this is to consider a different CSS framework for your new project. Even if the above issue is solved, the inherent issue with emotion appears that it's not compatible with how RSCs are intended to be rendered, so that most optimisations you expect to get from using them (like the VDOM not being as large and hydration times being lower) will not manifest when all your components are client components by necessity.

@francoisjacques
Copy link

Thank you @tom-streller-tutti! Is your minimal experiment something that can be shared for other to look at? It could be useful to create a "safe" boilerplate for others to use... for those who can't consider using a different styling engine than emotion for various reason.

@tihuan
Copy link

tihuan commented Jan 12, 2024

Following this thread, since our stack is also Next.js + Emotion + MUI and currently migrating from Next.js 12 to 14

I just tried adding /* @jsxImportSource react */ to src/app/layout.tsx per #2928 (comment), and somehow still getting You have illegal escape sequence in your template literal, most likely inside content's property value. I was hoping adding /* @jsxImportSource react */ would get rid of that error, but maybe it's not related 😆

@LuisGilGB
Copy link

For people trying to make the Next + Emotion + MUI combination work in Next 14, have you tried with next@14.0.1? As far as I could see when I did a migration from v13 to v14, v14.0.2 brought some changes in the way modules are resolved which made builds no possible at all because of how some dependencies were exported; but with v14.0.1 everything worked ok. Not the latest version (v14.0.4 currently); but I guess it's enough for most migration purposes.

@francoisjacques
Copy link

Following this thread, since our stack is also Next.js + Emotion + MUI and currently migrating from Next.js 12 to 14

I just tried adding /* @jsxImportSource react */ to src/app/layout.tsx per #2928 (comment), and somehow still getting You have illegal escape sequence in your template literal, most likely inside content's property value. I was hoping adding /* @jsxImportSource react */ would get rid of that error, but maybe it's not related 😆

Do you get it even when you disable turbo?

@siriwatknp
Copy link

Anybody from MUI or community working on an updated version of the next/mui example? https://github.com/mui/material-ui/tree/master/examples/material-ui-nextjs-ts We still need a separate client flle (with "use client") where ThemeProvider/CssBaseline is defined, right?

MUI Next.js examples are now using the integration package, see mui/material-ui#40199.

@tom-streller-tutti
Copy link

Thank you @tom-streller-tutti! Is your minimal experiment something that can be shared for other to look at? It could be useful to create a "safe" boilerplate for others to use... for those who can't consider using a different styling engine than emotion for various reason.

@francoisjacques I have create a public Repo with the minimal version I used: https://github.com/tom-streller-tutti/minimal-nx-nextjs-emotion-mui

It's not exactly minimal, as it also contains nx as a build tool. I also wrote about what I found out and other things in the README. I hope it helps.

@denu5
Copy link

denu5 commented Jan 17, 2024

@tom-streller-tutti fyi: what you mention in your rant readme is kind of what mui did a while ago on all components they export.

ps. das logo kenn ich, gruess us züri :)

mui/material-ui@a4afa9f#diff-404207b76c551306dc4629e283a6483cd30cff95b811d3bd57147b5fabca3b79

@oliviertassinari
Copy link

oliviertassinari commented Jan 21, 2024

As far as Material UI is concerned:

@Andarist
Copy link
Member

React 19 - to the best of my knowledge - is meant to allow using runtime CSS-in-JS libs in RSC. I'm playing with their Float APIs and it looks promising.

@bexoss
Copy link

bexoss commented Jan 27, 2024

The truth is it's not working well. I am keeping nextjs version to 12 because of this issues. I should have choose tailwind

@dzek69
Copy link

dzek69 commented Jan 27, 2024

@bexoss you can use v13, just don't use app router but pages router. It's safer to be on the newer version probably:)

@oliviertassinari
Copy link

oliviertassinari commented Feb 6, 2024

The truth is it's not working well. I am keeping nextjs version to 12 because of this issues.

@bexoss What issue did you experience?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests