Open karlhorky opened 1 year ago
🤔 Wonder if this new "Float" thing has anything that is useful here for enabling CSS-in-JS + React Server Components
cc @sebmarkbage
Some other CSS-in-JS ecosystem movement trying to support React Server Components:
Seems like Chakra UI is going to switch away from Emotion and to a zero runtime CSS-in-JS solution called Panda:
Zero runtime CSS-in-JS [Panda]
This is the most common and most challenging request we get from users.
Runtime CSS-in-JS and style props are powerful features that allow developers to build dynamic UI components that are composable, predictable, and easy to use. However, it comes at the cost of performance and runtime.
With the release of React Server Components, providing the ability to write Chakra UI components on the server has become crucial. This is a huge win for performance, development, and user experience.
We're building a new, framework-agnostic styling solution that keeps most of the great features of Chakra's styling system but extracts styles at build time. It'll also feature a PostCSS plugin that extracts styles at postcss run time during development.
Panda will leverage new modern platform features like CSS variables, cascade layers, and W3C token spec.
Too ease the blow for everybody who is struggling with this (including me), a little consideration:
If you were happy with emotion before, there is no difference right now. Client components render the same way, they render with SSR and are passed to the browser and render there as well.
The difference is that the leaves are bit higher up and you need to use wrapper for direct HTML elements.
Lot's of elements that do not actually render HTML elements can still use RSC, this means all your components that composite other components, like pages or layouts still benefit from RSC.
import { Box } from '@mui/material' // If you've patched @mui/material to have a 'use client' on top of the actual JS file.
export async function ThisIsAServerComponent() {
const bla = await fetch()
return <Box sx={{color: 'red'}}>Hello from RSC {bla}</Box>
}
There of course are long therm choices to be made to move to a build-time extraction of css-in-js or another solution, but this argument was always there.
@Andarist we are migrating to Next.js app
router and using server components by default, but are currently blocked by not knowing if MUI/emotion will be compatible with server components. We'd really appreciate confirmation on whether to expect MUI/emotion to support server components in the future and roughly when that could be possible, so we can plan and start migrating.
I've outlined the compatibility issues with MUI and server components, (naive) possible solutions and the blocked decision we're facing - whether to use MUI going forward. Lots of which has been discussed on the main MUI thread already but summarising again in case it's helpful for others reaching this thread!
Server components are not currently compatible with CSS-in-JS, a core feature of MUI and most component libraries, due to their closely coupled dependency, emotion. Dependency tree: MUI → emotion → CSS-in-JS
Next.js Docs highlight that CSS-in-JS is not currently supported and that Next.js/React are working on supporting this, although it’s not clear if CSS-in-JS will be supported for server components by Next.js/React alone, or more likely that library authors will also need to make changes. The next.js docs also state that MUI and emotion are also working on support, but there’s no confirmation of this or definitive answers about whether server components will be supported by MUI/emotion in the future.
MUI’s options Existing issue - #34826
Emotions options
MUI components use interactivity including event listeners, hooks, state and lifecycle effects. Inherently making them client components and not compatible with server components. For some MUI components this interactivity is always a requirement (e.g. all inputs, <Accordion>
, <Tooltip>
). However we also want to use non-interactive UI components in our server components - e.g. static text using <Typography>
or a basic styled <Card>
component with no actions. Even <Button>
components can be used in server components when using a static href link vs an onClick()
listener. Currently it isn’t possible to use MUI components in server components regardless of if we are using interactive/stateful features of a MUI component, because the listeners/hooks are still defined in the MUI component.
MUIs options Existing issue - #35993
<Box>
and server component <ServerBox>
import { Box } from '@mui/material';
and server component import { Box } from '@mui/material/server';
server
prop is present on the component <Box server={true}>
or automatically detected server componentFor those intending to use server components on a new project, or refactoring an existing project using MUI, the outlook/decision currently seems to be:
Disclaimer: I'm not an RSC expert so take this with a grain of salt.
The next.js docs also state that https://github.com/mui/material-ui/issues/34905 and https://github.com/emotion-js/emotion/issues/2928 are also working on support
Next.js rushed the release of their docs without consulting library authors. The mentioned Styled-Components "support" looks almost exactly the same as the Emotion support can look like (see the comment here). There is no special API in SC that integrates with RSC in any special way.
I expect that both SC and Emotion might be prone to subtle hydration bugs when using this approach though (for the majority of use cases they should be totally negligible. I don't want to raise panic over this as there is definitely nothing to panic about here, just mentioning for completeness and correctness. I don't want to claim that the mentioned support is ideal).
To bring better support to Emotion we are waiting for React's new APIs that should allow us to inject styles to the streamed response without having to "listen" on what's being streamed (this is essentially how useServerInsertedHTML
works today).
However we also want to use non-interactive UI components in our server components - e.g. static text using
or a basic styled component with no actions. Even
All of those are just client components - which doesn't mean that they can't be rendered on the server. Refetches might not benefit from SSR style-injection but that shouldn't be a big deal for the majority of use cases anyway. You can totally use those in RSC, as shown by @paales here
Currently it isn’t possible to use MUI components in server components regardless of if we are using interactive/stateful features of a MUI component, because the listeners/hooks are still defined in the MUI component.
This might require some changes in MUI. I'm not part of their team so you should raise that in their issue tracker. Likely the issue is just about adding 'use client';
to their entrypoints (something that Emotion should likely do as well?)
This whole style extraction thing sounds like a good chunk of work slowing down fast refresh and building times or am I just being overly anxious?
- Only a subset of Emotion could be supported in React Server Components - eg. the part that is statically extractable / zero-runtime
That would be similar to what react /next is doing, eg. not allowing for hooks in server components. If no hooks and no state are present, dynamic styling literally renders useless anyway... Im my opinion this is the way to go, especially considering that this would solve most of the 'performance problems' css-in-js is accused of 🍿
Having to use 2 styling libs in a project does not sound inviting imho, but it's the only way possible now 😞
As I understand it, the lack of the React Context API is the only reason why Emotion doesn't work with RSC.
What would prevent us from using the React 18 cache()
API to replace the React context when running with RSC? It's scoped to the server-side render cycle. So to the request with Next.js. One example of a library wrapping it: https://github.com/manvalls/server-only-context.
So it seems to me that we can make Emotion work with RSC, today, I wish I had time to work on this 🙃.
@oliviertassinari just for the record I am not sure about the example in the readme of this lib, more info here : https://github.com/manvalls/server-only-context/issues/4 But on the top of my head, for a lib like Emotion, what you want is a stateful object scoped to the current request React tree, so I think it would work.
I did a POC to test RSC + Client Side Component context support using React.cache
on RSC and React.useContext
on client components, it seems to work fine: https://github.com/joshwcomeau/dream-css-tool/pull/7. The main trick is:
/* eslint-disable react-hooks/rules-of-hooks */
import * as React from 'react';
export const useServerCache = React.cache(() => []);
let clientContext;
function getClientCache() {
if (!clientContext) {
clientContext = React.createContext([]);
}
return clientContext;
}
export function useCache() {
try {
// React Server Component
return useServerCache();
} catch (e) {
// React Client Component
const clientContext = getClientCache();
return React.useContext(clientContext);
}
}
We need to duplicate the context value. cc @Andarist
How I understand things working:
I looked a bit closer at how the React Context API missing on the server-side prevents Emotion to be RSC compatible. It seems to work fine: https://github.com/oliviertassinari/test-theme/blob/main/src/app/about/page.tsx at the condition of not supporting theme nesting in RSC.
The problem
I would like to have a Next.js 13
Layout
component (eg.RootLayout
) that is a Server Component, and have my styling for that layout also stay in Server Components (potentially even in the sameapp/layout.tsx
file). I only want to switch to a Client Component if there is some client-only features I'm using such asuseState
.This also aligns with the current guidance (as of Jan 2023) from the Next.js team to just leave everything a server component until you absolutely need a client component.
Proposed solution
It would be great if Emotion would be able to be used in React Server Components (without conversion to a Client Component)
Alternative solutions
'use client'
every time that a page should be styledAdditional context
Moved from this other issue about Next.js 13
app/
directory support: