emotion-js / emotion

👩‍🎤 CSS-in-JS library designed for high performance style composition
https://emotion.sh/
MIT License
17.32k stars 1.1k 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.

adamrneary commented 1 year ago

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 commented 1 year ago

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

emmatown commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

Reported a bug about the SWC Emotion transform plugin here:

Andarist commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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

songhobby commented 1 year ago

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

Rafcin commented 1 year ago

@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 commented 1 year ago

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

mi-na-bot commented 1 year ago

@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 commented 1 year ago

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

mi-na-bot commented 1 year ago

@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 commented 1 year ago

After talking with the Next.js team and helping them recognize the problem with useServerInsertedHTML and Suspense that issue has been fixed in https://github.com/vercel/next.js/pull/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)

snelsi commented 1 year ago

@Andarist Can you provide a CodeSandbox, please? I am getting the React.createContext is not a function error in the console even after wrapping my app with RootStyleRegistry

Andarist commented 1 year ago

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

fcisio commented 1 year ago

Happy to see progress on here! I was wondering if anyone could see jsxImportSource working in compiler (ts swc or babel)?

From my understanding, I don't think it will be possible. Instead, we might have to use the /** @jsxImportSource @emotion/react */ comment (per file).

Andarist commented 1 year ago

With Babel it's definitely possible - you might need to set the runtime: 'automatic' first though: https://babeljs.io/docs/en/babel-preset-react#runtime

fcisio commented 1 year ago

Thanks @Andarist I'll test with Babel in a little bit. I did test with TS SWC just now (with the stackblitz above), and that one doesn't seem to work.

ScreenShot 2022-11-18 at 10 57 46 AM

paales commented 1 year ago

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)

@Andarist What does this mean? Surely we can add emotion styles to our react server components? What value does a react server component have when it doesn't have any styles? Maybe I don't fully understand :)

Andarist commented 1 year ago

Yeah, this is a little bit confusing - essentially server components have some limitations (for good reasons, in this model). They also have some unique capabilities (such as being able to use async functions).

I hope that this answer can help to clarify the situation a little bit. Let me know if you have any further questions.

adamrneary commented 1 year ago

Hmm. Are we saying that Emotion will not be supporting styling server components? If so, we should be clear about this, as it is possibly reason enough to move off Emotion for some folks. My understanding is that server components do work with CSS modules, for example.

So for clarity's sake, we should probably not be discussing:

The case that I think we're eager to understand is for basic static components that do things like layout and do require styling to achieve that. Do we currently — and will we long term — have a gap where CSS Modules could work for styling these components but Emotion would not?

stefee commented 1 year ago

The case that I think we're eager to understand is for basic static components that do things like layout and do require styling to achieve that. Do we currently — and will we long term — have a gap where CSS Modules could work but Emotion would not?

Correct me if I'm wrong, but my understanding is that server and client components will both "work" in a SSR context, the only difference being that client components will also re-render and hydrate the client-side React tree. If you want to style server components (i.e. components which do not hydrate client-side) because that performance gain is important to you, then that may be a reason for you to ditch Emotion. If you are OK with hydration as is the current status quo, then you have nothing to be concerned about.

Is this incorrect?

adamrneary commented 1 year ago

Yes, we are trying to understand if we can style server components (i.e. components which do not hydrate client-side). Before figuring out if this is a reason to ditch Emotion, we want to understand if this is in fact the near- to mid-term plan.

paales commented 1 year ago

If you want to style server components (i.e. components which do not hydrate client-side) because that performance gain is important to you, then that may be a reason for you to ditch Emotion.

This the main benefit of RSC to offload work to the server instead of having to do it on the client and have a performance gain? This effectively makes emotion useless in the future? (and will create a few thousand hours of work to migrate to something else..)

Maybe this conclusion is premature, but I really don't follow. It can not be that emotioncss can not be used with react server components. Styling needs to happen in a RSC and the resulting HTML/CSS should be send to the browser. That is the whole idea behind server components?

KaddieZ commented 1 year ago

If you want to style server components (i.e. components which do not hydrate client-side) because that performance gain is important to you, then that may be a reason for you to ditch Emotion.

This the main benefit of RSC to offload work to the server instead of having to do it on the client and have a performance gain? This effectively makes emotion useless in the future? (and will create a few thousand hours of work to migrate to something else..)

Maybe this conclusion is premature, but I really don't follow. It can not be that emotioncss can not be used with react server components. Styling needs to happen in a RSC and the resulting HTML/CSS should be send to the browser. That is the whole idea behind server components?

I think it's a misunderstanding. Next.js with SSR already does render styled components on the server. However styled components do not work with react server components. But styled components work in client components. Client components can be rendered server side with Next. So you already have a performance gain from that if that's what you're looking for. What React Server Components brings to the table is being able to natively call server functions.

You can verify this by writing a client component with emotion or styled components inside, and check your network when you have loaded your page in your browser. Check the reponse content of your first request which rendrs the html of your page, it already has your css.

React server Components are still new, I think it's a bit early to ditch styled components because they dont yet render in RSC.

stefee commented 1 year ago

we are trying to understand if we can style server components

The question is answered already here.

Server Components were designed in a way that is at fundamental odds with CSS-in-JS. So don't expect Emotion (or other popular CSS-in-JS libs) to introduce support for them. They just don't allow us to integrate with them anyhow.

I am considering the question, why do we use a library like Emotion or Material or Chakra, etc. in the first place. A big benefit we get is reactivity, i.e. we can link the styles directly with React state. This is what enables us to have dynamic light/dark mode settings without page reload, for example. I think it fundamentally will not work to use these libraries in RSC because there is no reactivity or state in RSC - i.e. the goals of these libraries are juxtaposed to the goals of RSC.

However - to repeat what has been said already - since server Components can render client components, this does not mean Emotion cannot be used with server components, it only means that Emotion cannot be used directly by server components, if this makes sense.

itsminh99 commented 1 year ago

I'm using NextJS v13, emotion, MUIv5 and have a problem: While the page is loading(reload after cached), the css is broken(image huge size,...). How to fix?

https://user-images.githubusercontent.com/47772671/205214098-f4784547-6c71-4f84-af0a-3e34a0da7506.mov

mi-na-bot commented 1 year ago

I'm using NextJS v13, emotion, MUIv5 and have a problem: While the page is loading(reload after cached), the css is broken(image huge size,...). How to fix?

Screen.Recording.2022-12-02.at.10.53.12.mov

@Andarist This might be from hydrating <html>

Andarist commented 1 year ago

Hmm. Are we saying that Emotion will not be supporting styling server components? If so, we should be clear about this, as it is possibly reason enough to move off Emotion for some folks. My understanding is that server components do work with CSS modules, for example.

Yes, Server Components work with CSS modules.

The case that I think we're eager to understand is for basic static components that do things like layout and do require styling to achieve that. Do we currently — and will we long term — have a gap where CSS Modules could work for styling these components but Emotion would not?

Yes.

However, I think that you might be overestimating the need for using styles in your Server Components. From my PoV it's just much simpler for the team as a whole to limit the CSS usage to your Client Components. This way you won't have to juggle your components when you decide to add some interactivity to those components. In other words - not using styles in your Server Components makes your app (IMHO) more resilient to refactors and needless diffs in PRs.


@itsminh99 please provide a repro case for this. I can't know what's wrong if you provide just a video.

itsminh99 commented 1 year ago

@Andarist My repo: https://github.com/itsminh99/base-next13

Andarist commented 1 year ago

@itsminh99 you have two problems there:

  1. you didn't use explicit <head></head> in your layout (this is required and gonna be validated by Next soon, see here)
  2. you are only creating a single Emotion cache for the whole server's lifetime - this has to be created per request

You can fix both with this patch:

diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 63f0276..4ed3345 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -9,6 +9,7 @@ type RootLayoutProps = {
 const RootLayout = (props: RootLayoutProps) => {
   return (
     <html>
+      <head></head>
       <body>
         <AppProvider>
           <MainLayout {...props} />
diff --git a/src/contexts/EmotionProvider.tsx b/src/contexts/EmotionProvider.tsx
index 7ad613d..2f9aba1 100644
--- a/src/contexts/EmotionProvider.tsx
+++ b/src/contexts/EmotionProvider.tsx
@@ -46,7 +46,9 @@ const createEmotionCache = () => {
 const clientSideEmotionCache = createEmotionCache();

 const EmotionProvider = (props: EmotionProviderProps) => {
-  const [{ cache, flush }] = useState(clientSideEmotionCache);
+  const [{ cache, flush }] = useState(
+    isBrowser ? clientSideEmotionCache : createEmotionCache,
+  );

   useServerInsertedHTML(() => {
     const names = flush();
jaredatron commented 1 year ago

@Andarist My repo: https://github.com/itsminh99/base-next13

Would you mind putting your example back up?

itsminh99 commented 1 year ago

@deadlyicon You can watch it here: https://codesandbox.io/p/sandbox/next13-muiv5-6ydezl

Meir-p commented 1 year ago

Does someone configure the ColorScheme with the rootLayout ?

ODelibalta commented 1 year ago

Anyone has a working example of this with Chakra UI? The provided solution here does not work for me. I get

Warning: Extra attributes from the server: class
body
html
ReactDevOverlay@webpack-internal:///./node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:53:9
HotReload@webpack-internal:///./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:19:39
Router@webpack-internal:///./node_modules/next/dist/client/components/app-router.js:97:89
ErrorBoundaryHandler@webpack-internal:///./node_modules/next/dist/client/components/error-boundary.js:28:9
ErrorBoundary@webpack-internal:///./node_modules/next/dist/client/components/error-boundary.js:40:56
AppRouter
ServerRoot@webpack-internal:///./node_modules/next/dist/client/app-index.js:113:25
RSCComponent
Root@webpack-internal:///./node_modules/next/dist/client/app-index.js:130:25

https://github.com/chakra-ui/chakra-ui/discussions/7111 https://github.com/chakra-ui/chakra-ui-docs/issues/1292#issuecomment-1353007013

Andarist commented 1 year ago

We don't put any attributes on the body so I can only assume that this is not strictly related to Emotion.

ODelibalta commented 1 year ago

@Andarist I appreciate your response. Please disregard my previous message. I've manage to get rid of it by creating a new app using next create app. it is gone now but I am having a different issue when I implement your reply above https://github.com/emotion-js/emotion/issues/2928#issuecomment-1319747902 The warning I get is this

Warning: Each child in a list should have a unique "key" prop. See https://reactjs.org/link/warning-keys for more information.
    at style

I can toggle it on and off by taking in/out the RootStyleRegistry in my main app layout file

import "./globals.css";
import Chakra from "../lib/chakraProvider";
import RootStyleRegistry from "../lib/emotion";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      {/*
        <head /> will contain the components returned by the nearest parent
        head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
      */}
      <head />
      <body>
        <RootStyleRegistry>
          <Chakra>{children}</Chakra>
        </RootStyleRegistry>
      </body>
    </html>
  );
}

// chakra provider
"use client";

import { ChakraProvider } from "@chakra-ui/react";
import { theme as proTheme } from "@chakra-ui/pro-theme"; 

export default function Chakra({ children }: { children: React.ReactNode }) {
  return <ChakraProvider theme={proTheme}>{children}</ChakraProvider>;
}
EugeneMeles commented 1 year ago

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>;
}

Have a some problem with MUI5 when I switch theme mode (light/dark) in UI (( Dirty fix that addet useEffect with delete ssr style:

  React.useEffect(() => {
    const style = document.querySelector("[data-emotion^='css ']");
    if (style) {
      style.remove();
    }
  }, []);
francismanansala commented 1 year ago

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>;
}

Have a some problem with MUI5 when I switch theme mode (light/dark) in UI (( Dirty fix that addet useEffect with delete ssr style:

  React.useEffect(() => {
    const style = document.querySelector("[data-emotion^='css ']");
    if (style) {
      style.remove();
    }
  }, []);

The fix here didn't really work for my project. When I tried this it ruined a lot of my styles that I needed for some reason. I instead did a different dirty fix and removed the color and background-color the RootStyleRegistry initially inserts the styles. I find it works well with my initial styles and when toggling between light and dark modes.

'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;
}): React.ReactElement {
  const [{ cache, flush }] = useState(() => {
    const cache = createCache({ key: 'cache-key' })
    cache.compat = true
    const prevInsert = cache.insert
    let inserted: string[] = []
    cache.insert = (...args): string | void => {
      const serialized = args[1]
      if (cache.inserted[serialized.name] === undefined) {
        inserted.push(serialized.name)
      }
      return prevInsert(...args)
    }
    const flush = (): string[] => {
      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) {
      let style = cache.inserted[name]
      const removeThemeColors = typeof style === 'string' && style.indexOf('html{') === 0
      if (removeThemeColors) style = (style as string)
        .replace(/(body{[^}]*)(background-color:[^;]*;)/i, '$1')
        .replace(/(body{[^}]*)(color:[^;]*;)/i, '$1')
      styles += style
    }

    return (
      <style
        data-emotion={`${cache.key} test ${names.join(' ')}`}
        dangerouslySetInnerHTML={{
          __html: styles,
        }}
      />
    )
  })

  return <CacheProvider value={cache}>{children}</CacheProvider>
}
jaredatron commented 1 year ago

I attempted to use this hack and while it works I find it causes intermittent "SSR differs from Client render" errors which made development less than fun. :/

garronej commented 1 year ago

Hey,
If you want to abstract away all these shenanigans and just get Emotion working in AppDir, you can use the TSS's <NextAppDirEmotionCacheProvider /> it's just a wrapper for code that has been shared here though.

Hi @deadlyicon, about the random hydration error, I've opened an issue about it on Versel's repo.
Hope it will be fixed soon 🤞🏻

paluchi commented 1 year ago

Could someone gently tell me if my thoughts are right?

By my slim knowledge about this topic I understand that state is needed to use MUI, Styled Components or any other CSS-in-JS libraries, so client components would be needed to ose those libraries. In that case what is the reason to add a styles collector provider as client side component if all components that use those libraries would be already client sided.

If as far as I am going everything is right and clear then I could infer that CSS-in-JS will never be posible in SSR because of it's stateless capabilities. And refetching would not be a great option because of the fetch delay.

It would be very useful to know if my thoughts are well aligned and I think that by explaining all in a single answer the above concerns would make the undertandings about this problem more strong and centralized

cereallarceny commented 1 year ago

Could we at least know if this is something on the Emotion team's roadmap? For those looking to start projects that utilize server components, it's pretty limiting to have your entire UI framework be unable to be carried over with that new philosophy. Not passing critique or concern - just merely wanting to know if the Emotion team intends to do anything about this or if it's a more passing/distant concern. ☺️

Andarist commented 1 year ago

It's on my mind to provide better out-of-the-box options to integrate with both Next.js and other frameworks. However, when it comes to Next.js - you already can almost fully integrate using the snippets shared in this thread.

garronej commented 1 year ago

Hi @Andarist,
I'm pretty sure @cereallarceny is asking about being able to use emotion in server components.
A previous answer of yours mentioned that:

Server Components were designed in a way that is at fundamental odds with CSS-in-JS. So don't expect Emotion (or other popular CSS-in-JS libs) to introduce support for them. They just don't allow us to integrate with them anyhow.

Unless there is some new development, I think this is the answer people are looking for unfortunately... 😕

cereallarceny commented 1 year ago

That sounds like the writing is on the wall to me. Totally understand the reasoning, thanks for the response.