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

Emotion in React Server Components? #2978

Open
karlhorky opened this issue Jan 30, 2023 · 12 comments
Open

Emotion in React Server Components? #2978

karlhorky opened this issue Jan 30, 2023 · 12 comments

Comments

@karlhorky
Copy link

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
Copy link
Author

🤔 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
Copy link
Author

karlhorky commented Apr 13, 2023

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
Copy link

paales commented Apr 13, 2023

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
Copy link

annarhughes commented Jun 12, 2023

@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

  • Don’t support server components
  • Remove emotion in a significant refactor, similar to Chakra who are building a new library
  • Provide alternative server compatible components, e.g. switch/compile to CSS modules or use subset of emotion features

Emotions options

  • Don’t support server components
  • Remove CSS-in-JS in a significant refactor
  • Provide alternative solutions for server components, e.g. switch/compile to CSS modules or use subset of emotion features

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

  • Provide alternative MUI server-compatible components, removing the interactive or stateful functionality and relying purely on serialisable props.
    • e.g. Client component <Box> and server component <ServerBox>
    • e.g. Client component import { Box } from '@mui/material'; and server component import { Box } from '@mui/material/server';
  • Add functionality to switch component or strip listeners/hooks from a component if e.g. a server prop is present on the component <Box server={true}> or automatically detected server component

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
Copy link
Member

Disclaimer: I'm not an RSC expert so take this with a grain of salt.

The next.js docs also state that mui/material-ui#34905 and #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 components can be used in server components when using a static href link vs onClick() listener.

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
Copy link

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
Copy link

Housi commented Sep 7, 2023

  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
Copy link

oliviertassinari commented Jan 22, 2024

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
Copy link

@oliviertassinari just for the record I am not sure about the example in the readme of this lib, more info here : manvalls/server-only-context#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
Copy link

oliviertassinari commented Feb 9, 2024

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: joshwcomeau/dream-css-tool#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

Thomas-Billon added a commit to Thomas-Billon/NextJS_React_Portfolio_V2 that referenced this issue Mar 10, 2024
Managed to make twin.macro work with emotion package
Caused the following problems:
- Had to switch SWC to Babel
- Had to remove Next/Font
- Had to add 'use client' on layout
- Had to remove Metadata
- Had to manually edit scripts from react-three/drei package

Not worth it
Maybe later when this issue is solved: emotion-js/emotion#2978
@oliviertassinari
Copy link

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.

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

8 participants