emotion-js / emotion

πŸ‘©β€πŸŽ€ CSS-in-JS library designed for high performance style composition
https://emotion.sh/
MIT License
17.43k stars 1.11k forks source link

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

Open fcisio opened 1 year ago

fcisio commented 1 year ago

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.

karlhorky commented 1 year ago

Counterpoint to the last arguments:

What I personally don't like with having to use Client Components for every component that needs styling is the DX: the churn and switching back and forth between Server Components and Client Components. Creates a lot of extra files only for styling.

I think the zero-runtime options are looking more like the future of CSS-in-JS

bpossolo commented 1 year ago

the churn and switching back and forth between Server Components and Client Components. Creates a lot of extra files only for styling.

it’s an advantage vue has over react: single file component that contains everything (html, css, js)

I think the zero-runtime options are looking more like the future of CSS-in-JS

this would be great. I’m hoping mui goes this way, at least for the layout-related components so they can be used in RSC with no runtime js overhead

mi-na-bot commented 1 year ago

@karlhorky A strategy where Emotion was zero-runtime for server components but behaved normally for client components would be interesting to me.

paulm17 commented 1 year ago

Though it might not be the perfect solution for everyone, I thought I’d share it here in case anyone finds it useful in this interim period.

Thank you for posting this. I've starred the repo and while it's not something that I may use right away.

The following issue: https://github.com/poteboy/kuma-ui/issues/53 which mentioned hybrid is very interesting. I will be carefully watching your progress and please announce when you have this functionality. I'll definitely jump on it and take a look.

ghost commented 1 year ago

Reading this I visited a site https://levelup.gitconnected.com/using-material-ui-with-next-js-13-and-tailwind-css-41c201855dcf title: Using Material UI with Next.js 13 and Tailwind CSS (Part 1)

Part 2 handle "flickering" issue https://levelup.gitconnected.com/using-material-ui-with-next-js-13-and-tailwind-css-part-2-72d7e034baa9

Github repo (8month old) : https://github.com/tatleung/nextjs13-with-mui-and-tailwindcss

omerman commented 1 year ago

Hey, do any of you guys think this will be supported any time soon without marking all components as client components?

RaiMX commented 1 year ago

Hey, do any of you guys think this will be supported any time soon without marking all components as client components?

I think NO, it will not

cosmictoby commented 1 year ago

Hey, do any of you guys think this will be supported any time soon without marking all components as client components?

There's just far too much that will need to be rewritten IMO. Lots of context being used which cannot be used in server components obviously, so a different approach will need to be found without prop drilling. This will take a lot of time. The answer might not have even been invented yet.

JakeGinnivan commented 1 year ago

A project which shows potential for a replacement in this space is https://github.com/chakra-ui/panda

It supports static css extraction, but supports very similar APIs to emotion.

garronej commented 1 year ago

Hi @jaredatron,

Indeed, Panda and Emotion provide feature a similar API, however they are very different beast.

Panda operates on a zero-runtime basis, which implies that it does not allow for styles to be generated based on a component's internal state or props. For instance, consider the following example:

image

This particular example will not work in Panda due to the lack of dynamic styling capabilities.

When it comes to implementing dynamic styles with Panda, you're required to utilize CSS variables along with data-* attributes. This, however, can lead to more complex and less straightforward code. In contrast, Emotion shines with its capacity for runtime styling, allowing for more intuitive dynamic styles creation.

Therefore, while they might appear similar at first glance, Panda consciously trades off some developer experience for enhanced performance and greater ease of integration across various technologies.

PupoSDC commented 1 year ago

@garronej This particular example you provided is a very bad use case for styled components / emotion etc... every half a second you will be creating a brand new entry on a style sheet... this will eventually kill off your browser entirely.

The truth is that technologies like emotion and style components are a bit flawed like that and tradeoffs like what panda does are inevitable for this type of solution to work with RSC. The question is if we will see at some point a major release of SC/emotion that allows SC components to be used with an API/limitations similar to pandas, or we need to move on to other libraries and slowly abandon this project as legacy.

There will never be a drop in replacement.

garronej commented 1 year ago

This particular example you provided is a very bad use case for styled components / emotion etc...

You've made a valid point, albeit somewhat off-topic.

To illustrate further, here's another instance where Panda falls short, but Emotion proves successful and avoids any leakage:

"use client";  

import { useState, useEffect } from "react";
import { css }Β "../styled-system/css";  

export default function Home(){

    const [clickCount, setClickCount]= useState(0);

    return (
        <div 
            className={css({ color: clickCount % 2 === 0 ? "blue" : "red"  })}
            onClick={()=> setClickCount(clickCount + 1)}
        >
            Hello 🐼
       </div>
     );

}

I believe it's crucial to clarify that despite the similarities in their APIs, Panda isn't a direct, drop-in replacement for Emotion. One of the main advantages of CSS-in-JS, in my opignion, is its ability to generate dynamic styles, an aspect where Panda falls short.

paulm17 commented 1 year ago

One of the main advantages of CSS-in-JS, in my opinion, is its ability to generate dynamic styles

Emotion was basically the goat when it came to that.

My gut feeling is that Vercel has killed dynamic css-in-js and so far newly released libs only scratch the surface of what Emotion used to do.

I won't take away from the work that the authors have done: Libs like Panda 🐼 and Kuma 🐻 are awesome for what they do.

However... once you start moving into complex territory that Emotion was able to do easily due to its dynamic nature. Unfortunately things start to fall short.

Unless the emotion team starts to work on a next paradigm shift with a more complex build tool than what Panda / Kuma provides. I don't think progress is going to be made from here.

I'm happy to be wrong though.

ctretyak commented 1 year ago

Any updates or work on this feature? Mantine looks useless without it :(

paulm17 commented 1 year ago

Any updates or work on this feature? Mantine looks useless without it :(

Mantine dev is working on v7, check the discord for updates in the alpha channel.

siriwatknp commented 1 year ago
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,
        }}
      />
    );
  });

@Andarist This workaround has one flaw when <Global> is used in the app. The global styles is included in the one style tag and when the app runs on the client, a new <style data-emotion="css-global"> is duplicated.

I adapted the logic from https://github.com/emotion-js/emotion/blob/main/packages/server/src/create-instance/extract-critical-to-chunks.js:

const [{ cache, flush }] = useState(() => {
    const cache = createCache({ key: "css" });
    cache.compat = true;

    const flush = () => {
      const styles = [];
      const regularCssIds = [];
      let regularCss = "";

      Object.keys(cache.inserted).forEach((id) => {
        if (cache.registered[`${cache.key}-${id}`]) {
          // regular css can be added in one style tag
          regularCssIds.push(id);
          regularCss += cache.inserted[id];
        } else {
          // each global styles require a new entry so it can be independently flushed
          styles.push({
            key: `${cache.key}-global`,
            ids: [id],
            css: cache.inserted[id],
          });
        }
      });

      styles.push({ key: cache.key, ids: regularCssIds, css: regularCss });

      return styles;
    };
    return { cache, flush };
  });

  useServerInsertedHTML(() => {
    const styles = flush();
    return styles.map((style) => (
      <style
        data-emotion={`${style.key} ${style.ids.join(" ")}`}
        key={style.key}
        // eslint-disable-next-line react/no-danger
        dangerouslySetInnerHTML={{ __html: style.css }}
      />
    ));
  });

It works except that the <style> are duplicated:

image

Any suggestion ?

garronej commented 1 year ago

Hello @siriwatknp,

It appears that the recently updated TSS util may have resolved the issue you're encountering: https://github.com/garronej/tss-react/blob/main/src/next/appDir.tsx

I might be wrong but I was't able to reproduce the issue.

To test it out within your setup, you can simply copy and paste the code. Please don't hesitate to reach back out if the problem persists.

Best

siriwatknp commented 1 year ago

Hello @siriwatknp,

It appears that the recently updated TSS util may have resolved the issue you're encountering: https://github.com/garronej/tss-react/blob/main/src/next/appDir.tsx

I might be wrong but I was't able to reproduce the issue.

To test it out within your setup, you can simply copy and paste the code. Please don't hesitate to reach back out if the problem persists.

Best

It works great! To be honest, I don't really understand why the result is different between your code and mine πŸ˜….

garronej commented 1 year ago

It works great!

@siriwatknp Great! Happy I could help!
I guess the MUI example setup for Next App Router should be updated then.
I'm happy to submit a PR

siriwatknp commented 1 year ago

It works great!

@siriwatknp Great! Happy I could help! I guess the MUI example setup for Next App Router should be updated then. I'm happy to submit a PR

I created the PR already, thank you so much. 😘

haukurmar commented 1 year ago

Thank you @garronej and @siriwatknp for pointing this fix within tss-react and MUI.

I have created a repo where I adopted the NextAppDirEmotionCacheProvider from MUI and this seems to work.

Also added component to get global styles, I just needed to create a wrapper component which i called GlobalCss that has "use client"

Here is the repo: https://github.com/haukurmar/next-13-appdir-with-emotion it's also deployed to Vercel: https://next-13-appdir-with-emotion.vercel.app

Everything works, even when javascript is disabled.

I am just wondering if there are any drawbacks here since the components that have Emotion styling have to have the "use client" but they are still rendered server side.

Nico924 commented 1 year ago

Hello @haukurmar Your project works well, thanks !

However I know have another issue with a ui library package (A private one I wrote) that uses emotion.

Let's assume I import a Button components, if it comes from the package, it always create this error in the server part (so always a blank screen is first displayed) and then it works.

This is the unclear error

error Require stack:
- /Users/nicolasbernier/projects/next-13-appdir-with-emotion/.next/server/app/page.js
    at Object.react-dom/test-utils (/Users/nicolasbernier/projects/next-13-appdir-with-emotion/.next/server/app/page.js:943:18)
    [...]

If I copy paste the same component in the project, then I have no issue.

Do you have any idea ?

haukurmar commented 1 year ago

Not sure @Nico924 I also just created a small UI private npm package with a Button component and added it to the a page in my project, it worked fine.

I didn't add any transpilePackages to the Next app or anything like that, maybe you need to do that?

The component looks like this:

"use client"; /* @jsxImportSource @emotion/react / import { ReactNode } from "react";

type ButtonProps = { children?: ReactNode; };

const Button = (props: ButtonProps) => { return ( <button css={{ backgroundColor: "blue", color: "white", }}

{props.children} ); };

export { Button }; export type { ButtonProps };

How does yours look like?

Nico924 commented 1 year ago

I found the issue my bad, my library was exporting by default a component with its own ThemeProvider ignoring then the Wrapper of the next app and causing the issue.

So everything is working great with the wrapper πŸ‘Œ

huozhi commented 1 year ago

Next.js has published a new canary 13.4.20-canary.0 which allows you skip the jsx factory comment hack (/** @jsxImportSource @emotion/react */). Let me know if it works for y'all or there're any other leftover issues.

Andarist commented 1 year ago

For people reading this thread. This is an example of how you can use Emotion in /app directory in Next.js: https://github.com/vercel/next.js/tree/9ea6bc4bcb7bf9d63ed8c013ac1ccc45b5974fda/test/e2e/app-dir/emotion-js . This is basically the same as what has already been shared in this thread months ago.

We don't intend to make this setup easier right now because it's a one-time thing that you have to do in your app and you can forget about it afterward. We are still waiting for React Float to get published to make further changes to Emotion.

roxizhauk commented 1 year ago

Could anyone tell if what I figured out is a right way to use @emotion/react in Next.js?

The version of next is 13.4.19 since 13.4.20-canary didn't work for me. Server components works properly (so I don't need to mark all as "client").

awkaiser-tr commented 1 year ago

@roxizhauk install next@canary or next@13.4.20-canary.8


npm view next@canary
next@13.4.20-canary.8 | MIT | deps: 17 | versions: 2050
The React Framework
https://nextjs.org

bin: next

dist
.tarball: https://registry.npmjs.org/next/-/next-13.4.20-canary.8.tgz
.shasum: ec405f4872d1c979b28b747c92176f2032e26570
.integrity: sha512-9x1Q75bd41bV8vjewNv8GMARqbfjizUMgSaRO+tjF534LHIEOMmzAYAAsxZuoGwiTYEWBJyi2J4ANl89gQ/gaQ==
.unpackedSize: 53.3 MB

dependencies:
@next/env: 13.4.20-canary.8                  @next/swc-linux-x64-musl: 13.4.20-canary.8   caniuse-lite: ^1.0.30001406                  
@next/swc-darwin-arm64: 13.4.20-canary.8     @next/swc-win32-arm64-msvc: 13.4.20-canary.8 postcss: 8.4.14                              
@next/swc-darwin-x64: 13.4.20-canary.8       @next/swc-win32-ia32-msvc: 13.4.20-canary.8  styled-jsx: 5.1.1                            
@next/swc-linux-arm64-gnu: 13.4.20-canary.8  @next/swc-win32-x64-msvc: 13.4.20-canary.8   watchpack: 2.4.0                             
@next/swc-linux-arm64-musl: 13.4.20-canary.8 @swc/helpers: 0.5.1                          zod: 3.21.4                                  
@next/swc-linux-x64-gnu: 13.4.20-canary.8    busboy: 1.6.0                                

maintainers:
- rauchg <rauchg@gmail.com>
- timneutkens <timneutkens@icloud.com>
- vercel-release-bot <infra+release@vercel.com>

dist-tags:
canary: 13.4.20-canary.8  latest: 13.4.19           next-11: 11.1.4           next-12-2-6: 12.2.6       next-12-3-2: 12.3.4       

published an hour ago by vercel-release-bot <infra+release@vercel.com>
dukesx commented 1 year ago

For people reading this thread. This is an example of how you can use Emotion in /app directory in Next.js: https://github.com/vercel/next.js/tree/9ea6bc4bcb7bf9d63ed8c013ac1ccc45b5974fda/test/e2e/app-dir/emotion-js . This is basically the same as what has already been shared in this thread months ago.

We don't intend to make this setup easier right now because it's a one-time thing that you have to do in your app and you can forget about it afterward. We are still waiting for React Float to get published to make further changes to Emotion.

Hi @Andarist, what exactly is React Float ? Couldnt find anything related to this.

karlhorky commented 1 year ago

@dukesx you have to poke around Twitter and the React GitHub PRs to piece together most information about that:

LeopoldLerch commented 1 year ago

sees to be linked with #3108

neither the reference mentioned by @roxizhauk or @Andarist seem to work for me. As soon as I remove the jsximportsource from the tsconfig, Typescript fails because of the unknown "css"-Prop. As soon as I add it (and make a clean build, meaning I delete the .next-folder and run a build) I have the errors about import :

"Attempted import error: 'useLayoutEffect' is not exported from 'react' (imported as 'React')."

Does not make a difference if I add the jsximport globally to tsconfig or to just a file with the css-prop

funny is also, that in the Example @Andarist mentioned, there is no jsximportsource in the tsconfig, and also the "jsx" is set to "react", however, I cannot set the jsx-property, as Next resets it to preserver automatically at next build.

LeopoldLerch commented 1 year ago

I just set up 2 fresh next apps (both with Typescript), one with app and the other one with pages router. On both I immediately added the jsximportSource for "@emotion/react". Did nothing else. Not even changing something in the code or adding css altogether by props or something.

npm run build immediately failed for the app-router. Pages-Router worked.

Does not make a difference if i set jsximport in the tsconfig or the emotion: true in the next.js config. Seems to have the same effect

wachidmudi commented 1 year ago
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,
        }}
      />
    );
  });

@Andarist This workaround has one flaw when <Global> is used in the app. The global styles is included in the one style tag and when the app runs on the client, a new <style data-emotion="css-global"> is duplicated.

I adapted the logic from https://github.com/emotion-js/emotion/blob/main/packages/server/src/create-instance/extract-critical-to-chunks.js:

const [{ cache, flush }] = useState(() => {
    const cache = createCache({ key: "css" });
    cache.compat = true;

    const flush = () => {
      const styles = [];
      const regularCssIds = [];
      let regularCss = "";

      Object.keys(cache.inserted).forEach((id) => {
        if (cache.registered[`${cache.key}-${id}`]) {
          // regular css can be added in one style tag
          regularCssIds.push(id);
          regularCss += cache.inserted[id];
        } else {
          // each global styles require a new entry so it can be independently flushed
          styles.push({
            key: `${cache.key}-global`,
            ids: [id],
            css: cache.inserted[id],
          });
        }
      });

      styles.push({ key: cache.key, ids: regularCssIds, css: regularCss });

      return styles;
    };
    return { cache, flush };
  });

  useServerInsertedHTML(() => {
    const styles = flush();
    return styles.map((style) => (
      <style
        data-emotion={`${style.key} ${style.ids.join(" ")}`}
        key={style.key}
        // eslint-disable-next-line react/no-danger
        dangerouslySetInnerHTML={{ __html: style.css }}
      />
    ));
  });

It works except that the <style> are duplicated:

image

Any suggestion ?

It might be caused by useServerInsertedHTML from next/navigation, as it's also occurred in this https://github.com/vercel/next.js/discussions/49354#discussioncomment-6279917

How about changing the code to be something like this :

const isServerInserted = useRef(false);

useServerInsertedHTML(() => {
  if (!isServerInserted.current) {
    isServerInserted.current = true;
    const styles = flush();
    return styles.map((style) => (
      <style
        data-emotion={`${style.key} ${style.ids.join(" ")}`}
        key={style.key}
        // eslint-disable-next-line react/no-danger
        dangerouslySetInnerHTML={{ __html: style.css }}
      />
    ));
  }
});
shaunwarman commented 11 months ago

Just wanted to check in on the state of this thread. Is there a list of potential workarounds or a set of in draft PRs for support to track?

AshConnolly commented 11 months ago

The suggested solution from @Andarist (who's awesome and super helpful!) was working for me once, but now doesn't seem to be working. This stackblitz worked fine a few months ago and is now failing - https://stackblitz.com/edit/stackblitz-starters-nn5hrk?file=app%2Femotion-root-style-registry.js

With this error:

Screenshot 2023-10-25 at 21 00 40

Anyone got an example of a working solution?

I've looked at other css-in-js tools but they look too limiting. Nothing is as pleasant as emotionCSS! πŸ˜…

alessandrojcm commented 11 months ago

The suggested solution from @Andarist (who's awesome and super helpful!) was working for me once, but now doesn't seem to be working. This stackblitz worked fine a few months ago and is now failing - https://stackblitz.com/edit/stackblitz-starters-nn5hrk?file=app%2Femotion-root-style-registry.js

With this error:

Screenshot 2023-10-25 at 21 00 40

Anyone got an example of a working solution?

I've looked at other css-in-js tools but they look too limiting. Nothing is as pleasant as emotionCSS! πŸ˜…

I had the same issue, you need to add the /* @jsxImportSource react */ pragma to all the server components files otherwise it uses the emotion pragma by default.

See the modified stack blitz https://stackblitz.com/edit/stackblitz-starters-qpumye?file=app%2Fpage.tsx

budokans commented 10 months ago
 const [{ cache, flush }] = useState(() => {

Setting initial state with no intention of updating it. Why not just define a constant?

mi-na-bot commented 10 months ago

MUI + Emotion has been working great for me in the app directory for a while using the code in tss-react. Is the Next.js documentation describing Emotion as unsupported inaccurate now? Don't the other "supported" options also have the limitation of not working in server components?

mi-na-bot commented 10 months ago

@budokans This is a very good question. The examples here Styling: CSS-inJS all do the same thing, so presumably (hopefully) there is some logic to it.

abriginets commented 10 months ago

MUI + Emotion has been working great for me in the app directory for a while using the code in tss-react. Is the Next.js documentation describing Emotion as unsupported inaccurate now? Don't the other "supported" options also have the limitation of not working in server components?

Emotion works well in app dir... When it's SSR'ed. The whole point of app dir is that components by default are Server Components while Emotion styling cannot be executed on server. I am not so very familiar with how Emotion works but essentially it injects css "on the fly" (side-effect) while Server Components must not have any side effects (pure functions).

quangnmwork commented 10 months ago

The suggested solution from @Andarist (who's awesome and super helpful!) was working for me once, but now doesn't seem to be working. This stackblitz worked fine a few months ago and is now failing - https://stackblitz.com/edit/stackblitz-starters-nn5hrk?file=app%2Femotion-root-style-registry.js With this error:

Screenshot 2023-10-25 at 21 00 40

Anyone got an example of a working solution? I've looked at other css-in-js tools but they look too limiting. Nothing is as pleasant as emotionCSS! πŸ˜…

I had the same issue, you need to add the /* @jsxImportSource react */ pragma to all the server components files otherwise it uses the emotion pragma by default.

See the modified stack blitz https://stackblitz.com/edit/stackblitz-starters-qpumye?file=app%2Fpage.tsx

This worked for me

dcruzb commented 10 months ago

I'm waiting for this to be resolved to start migrating to Next. But what I'm understanding now, based on recent comments, is that there is no longer a problem as long as we use the pragma that @quangnmwork quoted?

bpossolo commented 10 months ago

@dcruzb I’ve been using nextjs /app directory server and client components plus mui/emotion for over a year. I don’t have that pragma at the top of all my server components and it works just fine.

schoenwaldnils commented 10 months ago

I'm on next@14.0.2 and MUI and the app directory just work fine now for me. The only thing is that components that use MUI components and therefore require the ThemeContext need to be a client component aka have the 'use client' on top

stunaz commented 10 months ago

@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 commented 10 months ago

@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 commented 9 months ago

Still not working with version 14?

LuisGilGB commented 9 months ago

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 commented 9 months ago

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 commented 8 months ago

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:

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.