CSS-in-JS

Warning: CSS-in-JS libraries which require runtime JavaScript are not currently supported in Server Components. Using CSS-in-JS with newer React features like Server Components and Streaming requires library authors to support the latest version of React, including concurrent rendering.

We’re working with the React team on upstream APIs to handle CSS and JavaScript assets with support for React Server Components and streaming architecture.

The following libraries are supported in Client Components in the app directory:

Note: We're testing out different CSS-in-JS libraries and we'll be adding more examples for libraries that support React 18 features and/or the app directory.

If you want to style Server Components, we recommend using CSS Modules or other solutions that output CSS files, like PostCSS or Tailwind CSS.

Configuring CSS-in-JS in app

Configuring CSS-in-JS is a three-step opt-in process that involves:

  1. A style registry to collect all CSS rules in a render.
  2. The new useServerInsertedHTML hook to inject rules before any content that might use them.
  3. A client component that wraps your app with the style registry during initial server-side rendering.

styled-jsx

Using styled-jsx in client components requires using v5.1.0. First, create a new registry:

app/about/registry.tsx
'use client';

import React, { useState } from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import { StyleRegistry, createStyleRegistry } from 'styled-jsx';

type ChildProps = { children: React.ReactNode };

export function useStyledJsxRegistry() {
  const [jsxStyleRegistry] = useState(() => createStyleRegistry());

  function styledJsxFlushEffect() {
    const styles = jsxStyleRegistry.styles();
    jsxStyleRegistry.flush();
    return <>{styles}</>;
  }

  function StyledJsxRegistry({ children }: ChildProps) {
    return (
      <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>
    );
  }

  return [StyledJsxRegistry, styledJsxFlushEffect] as const;
}

export default function StyledJsxRegistry({
  children,
}: {
  children: React.ReactNode;
}) {
  const [StyledJsxRegistry, styledJsxFlushEffect] = useStyledJsxRegistry();

  useServerInsertedHTML(() => {
    return (
      <>
        {styledJsxFlushEffect()}
      </>
    );
  });

  return <StyledJsxRegistry>{children}</StyledJsxRegistry>;
}

Then, wrap your layout.js or page.js with the registry. This could also be your root layout:

app/about/layout.js
import StyledJsxRegistry from './registry'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <StyledJsxRegistry>
      {children}
    </StyledJsxRegistry>
  )
}

Styled Components

Below is an example of how to configure styled-components:

  1. Use the styled-components API to create a global registry component to collect all CSS style rules generated during a render, and a function to return those rules:
lib/styled-components.tsx
import React from 'react';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';

export function useStyledComponentsRegistry() {
  const [styledComponentsStyleSheet] = React.useState(
    () => new ServerStyleSheet(),
  );

  const styledComponentsFlushEffect = () => {
    const styles = styledComponentsStyleSheet.getStyleElement();
    // Alternatively, you can use `styledComponentsStyleSheet.seal()`
    // But when using Suspense boundaries, the styles should be cleared:
    styledComponentsStyleSheet.instance.clearTag();
    return <>{styles}</>;
  };

  const StyledComponentsRegistry = ({
    children,
  }: {
    children: React.ReactNode;
  }) => (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children as React.ReactElement}
    </StyleSheetManager>
  );

  return [StyledComponentsRegistry, styledComponentsFlushEffect] as const;
}
  1. Create a client component that uses the useServerInsertedHTML hook to inject the styles collected in the registry into the <head> HTML tag in the root layout.
app/RootStyleRegistry.tsx
'use client';

import { useStyledComponentsRegistry } from '../lib/styled-components';
import { useServerInsertedHTML } from 'next/navigation';

export default function RootStyleRegistry({
  children,
}: {
  children: React.ReactNode;
}) {
  const [StyledComponentsRegistry, styledComponentsFlushEffect] =
    useStyledComponentsRegistry();

  useServerInsertedHTML(() => {
    return <>{styledComponentsFlushEffect()}</>;
  });

  return <StyledComponentsRegistry>{children}</StyledComponentsRegistry>;
}
  1. Wrap the children of the root layout with the style registry component:
app/layout.js
import RootStyleRegistry from './RootStyleRegistry';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <RootStyleRegistry>{children}</RootStyleRegistry>
      </body>
    </html>
  );
}

Good to know:

During server rendering, styles will be extracted to a global registry and flushed to the <head> of your HTML. This ensures the style rules are placed before any content that might use them. In the future, we may use an upcoming React feature to determine where to inject the styles.

During streaming, styles from each chunk will be collected and appended to existing styles. After client-side hydration is complete, styled-components will take over as usual and inject any further dynamic styles.

We specifically use a Client Component at the top level of the tree for the style registry because it's more efficient to extract CSS rules this way. It avoids re-generating styles on subsequent server renders, and prevents them from being sent in the Server Component payload.