CSS-in-JS

CSS-in-JS and Zero-Runtime Alternatives

A runtime CSS-in-JS library builds a stylesheet while React 7,897 renders, by injecting rules into the document. A Server Component renders on the server, once, with no document to inject into and no client bundle to carry the library. So every runtime library in the App Router is confined to Client Components and needs a registry that collects the rules from the server pass and flushes them into the streamed HTML ahead of the markup that uses them: a registry object, the useServerInsertedHTML hook, and a Client Component wrapping the tree.

lib/registry.tsx — a styled-components registryTSX
"use client"; // useState from react, useServerInsertedHTML from next/navigation,
              // ServerStyleSheet and StyleSheetManager from styled-components
export default function Registry({ children }: { children: React.ReactNode }) {
  const [sheet] = useState(() => new ServerStyleSheet());
  useServerInsertedHTML(() => {
    const styles = sheet.getStyleElement();
    sheet.instance.clearTag();
    return <>{styles}</>;
  });
  if (typeof window !== "undefined") return <>{children}</>;
  return <StyleSheetManager sheet={sheet.instance}>{children}</StyleSheetManager>;
}

Wrap children in the root layout with it and set compiler: { styledComponents: true } in next.config.ts. styled-jsx, which ships inside Next.js 10,514 already, follows the same shape with createStyleRegistry.

CSS-in-JS options for the App Router, compared by when styles are generated
Library Styles generated Server Components Notes
styled-components 6.5.3 247,596 at render Client only registry plus SWC 553,439 flag
vanilla-extract 1.21.2 at build yes typed .css.ts files
PandaCSS, StyleX at build yes emit utility classes
Emotion 643,768 at render not yet support still in progress

Anything resolved at build time produces a plain stylesheet, so it needs no registry, no client boundary and no runtime kilobytes. vanilla-extract (vanilla-extract.style (https://vanilla-extract.style), MIT) is the closest thing to typed CSS Modules: you write style({ padding: 16 }) in a .css.ts file, the build evaluates it and emits a class name, and TypeScript checks the token names.