Why styled-components and emotion struggle
Both inject styles at runtime via JavaScript. Server Components ship zero JS to the client; runtime CSS-in-JS therefore can't work in a Server Component. The directive boundary becomes a hard wall.
| Approach | Server Components | Client Components |
|---|---|---|
| CSS Modules | ✅ | ✅ |
| Tailwind CSS | ✅ | ✅ |
| styled-components | ❌ | ✅ (with registry) |
| emotion | ❌ | ✅ (with registry) |
The registry pattern
If you must keep CSS-in-JS, wrap your app in a Client "style registry" that collects styles during server rendering and flushes them into the response. Performance is a tax you pay forever.
The clean answer
For new projects, use Tailwind or CSS Modules. They run everywhere and cost nothing at runtime.
Prefer build-time or static styling for Server Components. If a runtime CSS-in-JS library is required, isolate it behind a Client Component and install the framework's registry pattern at the smallest boundary that can collect server-rendered styles. Adding a registry can make a library render, but it does not restore Server Component benefits inside a client-marked subtree. Theme providers, runtime injection, and hydration work still enlarge the client surface and complicate streaming.
Render the styled page with JavaScript disabled, inspect the initial HTML for styles, and watch for flashes or hydration warnings under streaming. Compare the client bundle and component boundary with a CSS Module or Tailwind version.