Open fcisio opened 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
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
@karlhorky A strategy where Emotion was zero-runtime for server components but behaved normally for client components would be interesting to me.
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.
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
Hey, do any of you guys think this will be supported any time soon without marking all components as client components?
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
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.
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.
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:
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.
@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.
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.
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.
Any updates or work on this feature? Mantine looks useless without it :(
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.
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:
Any suggestion ?
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
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 π .
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
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. π
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
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.
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 ?
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?
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 π
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.
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.
Could anyone tell if what I figured out is a right way to use @emotion/react
in Next.js?
compiler.emotion
from next.config.js
"jsxImportSource": "@emotion/react"
from tsconfig.json
/** @jsxImportSource @emotion/react */
only when applying css
property to an elementThe 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").
@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>
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.
@dukesx you have to poke around Twitter and the React GitHub PRs to piece together most information about that:
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.
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
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: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 }}
/>
));
}
});
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?
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:
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! π
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:
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
const [{ cache, flush }] = useState(() => {
Setting initial state with no intention of updating it. Why not just define a constant?
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?
@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.
MUI + Emotion has been working great for me in the
app
directory for a while using the code intss-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).
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:
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
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?
@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.
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
@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
@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.
Still not working with version 14?
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.
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?
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:
<AppRouterCacheProvider/>
from @mui/material-nextjs - an implementation of the caching provider provided in earlier comments."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/* @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.
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.