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 · 168 comments
Open

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

fcisio opened this issue Oct 25, 2022 · 168 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

@mi-na-bot
Copy link

mi-na-bot 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

@mi-na-bot
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)

@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?

@mandymozart
Copy link

This is so sad. React moving to ssr and no serious support for real css encapsulation. And tailwind is so much going the very wrong direction. I love css and I hate the tailwind abstractions.

I actually really consider going back to global css style bundles or post css imports. For a graphic background person like me this is such a drag. Other frameworks like angular and web components have made much better moves keeping some sort of real encapsulation possible as suggested by W3C

@denu5
Copy link

denu5 commented Jul 2, 2024

@mandymozart

there are plenty of new css in js solutions

https://griffel.js.org
https://linaria.dev

https://mui.com/blog/introducing-pigment-css/

@garronej
Copy link
Contributor

garronej commented Jul 2, 2024

there are plenty of new css in js solutions

Yeah, but all those solutions that implement static extraction constitute a massive downgrade in DX and expressiveness compared to dynamic styles.

Being able to define your styles based on the component states, props, and ambient theme is very powerful.

I mean, sure, a solutions like Griffel and tss-react does look the same on the surface, but Griffel comes with serious limitations that tss-react or Emotion does not have.

@mandymozart
Copy link

mandymozart commented Jul 3, 2024

@denu5 thanks for the hints. I was researching a little and since I usually am super sceptical when switching tech, none of the things I found convinced me. Linaria looks fun, because I can almost write css as it is supposed to.

@garronej
Mui I was always a bit sceptical because it comes with its own components and my experience in some corporate large scale projects was that component libraries after some time all deteriorate, and since I am a UI designer by nature usually have to be modes to a extend that makes the underlying components ugly mutants wrapped in abstractions.

But hey, emotions was also just a work around for something I was so used to from dart and polymer and webcomponents and even angular. Actual shadow and shady dom :-)

I like that you mention expressiveness and the lack thereof. Yes, I think css is a super expressive language and it's poetry. I do this for 25 years since I was a teenager and it's my way of describing the world. It's even more my language than English. It's part of my dna. So perhapse I am biased haha but it is hard enough to change my grammar every 6 months in the web anyhow. (Insert Frontend dev laugh)

@1NF053C
Copy link

1NF053C commented Jul 31, 2024

Does Next.js 14.2.5 support the css property with @emotion/react ?

Do we need to follow these steps to enable it: https://nextjs.org/docs/app/building-your-application/styling/css-in-js#configuring-css-in-js-in-app ?

Does anyone have an example of doing so?

By default it does not seem supported, even with jsx pragma at top of file:

/** @jsx jsx */
import { jsx } from '@emotion/react';
Type '{ children: Element; css: { position: string; width: number; height: number; '[data-name=bg]': { color: string; }; '[data-name=line]': { color: string; }; }; }' is not assignable to type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'.
  Property 'css' does not exist on type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'

Thank you.

bear-bear-bear added a commit to bear-bear-bear/blueone that referenced this issue Aug 18, 2024
- emotion-js/emotion#2928
- emotion이 next app router 계속 미지원이라서 변경
@juliangorge
Copy link

Hi! Is there any news about this topic?

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