emotion-js / emotion

👩‍🎤 CSS-in-JS library designed for high performance style composition
https://emotion.sh/
MIT License
17.5k stars 1.11k forks source link

Emotion in React Server Components? #2978

Open karlhorky opened 1 year ago

karlhorky commented 1 year ago

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 same app/layout.tsx file). I only want to switch to a Client Component if there is some client-only features I'm using such as useState.

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

  1. Only a subset of Emotion could be supported in React Server Components - eg. the part that is statically extractable / zero-runtime
  2. Use Client Components via 'use client' every time that a page should be styled
  3. Switch to a different styling solution which can be used in React Server Components (eg. CSS Modules)

Additional context

Moved from this other issue about Next.js 13 app/ directory support:

karlhorky commented 1 year ago

🤔 Wonder if this new "Float" thing has anything that is useful here for enabling CSS-in-JS + React Server Components

Screenshot 2023-01-30 at 16 12 55

cc @sebmarkbage

karlhorky commented 1 year ago

Some other CSS-in-JS ecosystem movement trying to support React Server Components:

karlhorky commented 1 year ago

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.

https://www.adebayosegun.com/blog/the-future-of-chakra-ui

paales commented 1 year ago

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.

annarhughes commented 1 year ago

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

Compatibility issues

CSS-in-JS

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

Dynamic components

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

Current server components + MUI project migration options

For those intending to use server components on a new project, or refactoring an existing project using MUI, the outlook/decision currently seems to be:

  1. Assume (a) MUI will support server components and/or (b) Next.js/React will find a solution to handling JS-in-CSS, and continue building with MUI. Leave all components as client for now, then add back server components when MUI supports them. Tradeoff: likely refactoring required.
  2. Assume MUI will not support server components for the foreseeable future. Build all UI in client components only and continue building with MUI. Use server components only as wrappers to provide data to UI client components, or don’t use server components at all. Tradeoff: losing performance benefits of server components, gaining MUI.
  3. Remove MUI as the general component library and replace with a CSS solution like Tailwind which works well with server components. MUI could still be used for complex components like date pickers and advanced grids, which are client components anyway by their interactivity; however setting up theming for two different styling solutions is not ideal. Tradeoff: losing MUI, gaining server components performance benefits.
Andarist commented 1 year ago

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

robertpiosik commented 1 year ago

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?

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

oliviertassinari commented 9 months ago

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 🙃.

eric-burel commented 9 months ago

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

oliviertassinari commented 9 months ago

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:

SCR-20240221-oups
oliviertassinari commented 1 month ago

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.