Emotion Server-Side Rendering Utilities
@emotion/server is a critical package within the Emotion CSS-in-JS ecosystem, primarily designed to facilitate efficient server-side rendering (SSR) of styled React applications. Its core functionality involves extracting and inlining only the 'critical CSS' required for the initial page load, thereby preventing flashes of unstyled content (FOUC) and improving perceived performance. The package is part of the Emotion v11 stable release, which introduced significant TypeScript improvements and internal shifts to React Hooks. Emotion maintains a consistent, modular release cadence across its packages, with frequent patch updates and coordinated minor/major versions. A key differentiator is its deep integration with the `@emotion/react` and `@emotion/cache` packages, providing robust and performant solutions for complex SSR setups, including support for React's streaming APIs, though this often requires more advanced configurations.
Common errors
-
Error: Hydration failed because the initial UI does not match what was rendered on the server.
cause Client-side React is trying to hydrate a DOM tree that differs from the server-rendered HTML, often due to mismatched Emotion styles, incorrect cache setup, or missing `hydrate` call.fixEnsure the same Emotion cache key and configuration are used on both server and client. Verify that `CacheProvider` wraps your application during SSR. If using `extractCritical`, ensure the extracted `ids` are passed to the client and `hydrate(ids)` is called. -
ReferenceError: navigator is not defined
cause This typically occurs when `createCache` from `@emotion/cache` is invoked directly in a Node.js (server) environment without providing a suitable `container` or `stylisPlugins` option if browser-specific APIs are implicitly accessed.fixEnsure `createCache` is called with the `key` option and potentially a custom `container` or `stylisPlugins` if non-browser defaults are an issue. Creating a cache per request on the server also helps isolate styles. -
TypeError: Cannot read properties of undefined (reading 'sheet')
cause This error often indicates that the Emotion cache instance or the `CacheProvider` is not correctly set up or accessible within the React component tree during SSR.fixDouble-check that `createCache` is correctly called, and the resulting `cache` object is passed to `<CacheProvider value={cache}>` wrapping your application on the server. Verify that all Emotion-related packages are compatible versions. -
Error: @emotion/react's CacheProvider was not found.
cause The Emotion context, provided by `CacheProvider`, is missing from the component tree, preventing Emotion components from accessing the necessary cache.fixEnsure that your entire application, especially the root component being rendered server-side, is wrapped within `<CacheProvider value={cache}>` where `cache` is an instance created by `createCache`.
Warnings
- breaking Migrating from Emotion v10 to v11 involves significant breaking changes, including package renames (e.g., `@emotion/core` to `@emotion/react`), and a mandatory `key` option when initializing Emotion's cache via `createCache`. Ensure all Emotion-related packages are upgraded to compatible v11 versions.
- gotcha Incorrect or missing `CacheProvider` setup on the server or a mismatch between server and client-side generated styles can lead to hydration errors (e.g., UI differences or style re-insertion). Each server render should typically use a new `EmotionCache` instance.
- gotcha Emotion's 'default approach' for SSR (without explicit critical extraction) can interfere with `:nth-child` or similar selectors due to style tags being inserted directly into the markup. The advanced approach using `extractCritical` or `createEmotionServer` avoids this.
- deprecated The `renderStylesToString` function from `@emotion/server` is largely superseded by `extractCritical` for comprehensive critical CSS extraction and `renderStylesToNodeStream` for React 18 streaming. While it still works, `extractCritical` provides more control over the extracted CSS and IDs.
- gotcha Integrating Emotion with React 18's streaming SSR (`renderToPipeableStream`) is significantly more complex than traditional `renderToString`. `emotion-server` provides `renderStylesToNodeStream`, but advanced setups often require custom transform streams or specific framework integrations.
Install
-
npm install emotion-server -
yarn add emotion-server -
pnpm add emotion-server
Imports
- extractCritical
const { extractCritical } = require('@emotion/server');import { extractCritical } from '@emotion/server'; - renderStylesToNodeStream
const { renderStylesToNodeStream } = require('@emotion/server');import { renderStylesToNodeStream } from '@emotion/server'; - CacheProvider
import { CacheProvider } from '@emotion/core';import { CacheProvider } from '@emotion/react'; - createCache
import { createCache } from '@emotion/cache'; // If default export const createCache = require('@emotion/cache'); // Missing .default for default exportimport createCache from '@emotion/cache';
Quickstart
import ReactDOMServer from 'react-dom/server';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { extractCritical } from '@emotion/server';
import { css } from '@emotion/react';
// A simple Emotion-styled React component
const MyStyledComponent = () => (
<div
css={css`
color: hotpink;
background-color: lightblue;
padding: 1rem;
border-radius: 8px;
&:hover {
color: white;
}
`}
>
Hello from Emotion SSR!
</div>
);
// Create a new Emotion cache for the server request
// The 'key' option is mandatory since Emotion v11
const cache = createCache({ key: 'my-app' });
// Render the component to a string and extract critical CSS
const { html, css: criticalCss, ids } = extractCritical(
ReactDOMServer.renderToString(
<CacheProvider value={cache}>
<MyStyledComponent />
</CacheProvider>
)
);
console.log('--- Critical CSS ---');
console.log(criticalCss);
console.log('\n--- Rendered HTML ---');
console.log(`<style data-emotion="${cache.key}-${ids.join(' ')}">${criticalCss}</style>${html}`);
// In a real application, 'criticalCss' would be injected into the <head> of the HTML document.
// The 'ids' should also be passed to the client for proper hydration.
// Example of how to integrate into a full HTML document (simplified):
const fullHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Emotion SSR Example</title>
<style data-emotion="${cache.key}-${ids.join(' ')}">${criticalCss}</style>
</head>
<body>
<div id="root">${html}</div>
<script>
// On the client, hydrate with the same cache key and potentially call emotion/css hydrate function
// import { hydrate } from '@emotion/css';
// import createCache from '@emotion/cache';
// const cache = createCache({ key: 'my-app' });
// hydrate(${JSON.stringify(ids)});
// ReactDOMClient.hydrateRoot(document.getElementById('root'), <CacheProvider value={cache}><MyStyledComponent /></CacheProvider>);
</script>
</body>
</html>
`;
console.log('\n--- Full HTML Structure (simplified) ---');
console.log(fullHtml);