styled-components provides complete server-side rendering (SSR) support with style extraction, hydration, and server-side style sheet management for production applications and SEO optimization.
The ServerStyleSheet class collects styles during server-side rendering and provides methods to extract them for inclusion in the HTML document.
class ServerStyleSheet {
collectStyles(tree: React.ReactElement): React.ReactElement;
getStyleTags(): string;
getStyleElement(): React.ReactElement[];
seal(): void;
clearTag(): void;
instance: StyleSheet;
}Usage Examples:
import { ServerStyleSheet } from 'styled-components';
import { renderToString } from 'react-dom/server';
function renderApp(App) {
const sheet = new ServerStyleSheet();
try {
// Wrap your app with collectStyles
const html = renderToString(
sheet.collectStyles(<App />)
);
// Extract the styles as string
const styleTags = sheet.getStyleTags();
return {
html,
styleTags
};
} catch (error) {
console.error('SSR Error:', error);
throw error;
} finally {
sheet.seal();
}
}
// Usage in server
app.get('*', (req, res) => {
const { html, styleTags } = renderApp(<App />);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
${styleTags}
</head>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}import { renderToNodeStream } from 'react-dom/server';
import { ServerStyleSheet } from 'styled-components';
app.get('*', (req, res) => {
const sheet = new ServerStyleSheet();
// Send initial HTML
res.write(`
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root">
`);
try {
const stream = renderToNodeStream(
sheet.collectStyles(<App />)
);
stream.pipe(res, { end: false });
stream.on('end', () => {
// Inject styles before closing body
const styleTags = sheet.getStyleTags();
res.write(`
</div>
${styleTags}
<script src="/bundle.js"></script>
</body>
</html>
`);
res.end();
sheet.seal();
});
} catch (error) {
console.error('Streaming SSR Error:', error);
sheet.seal();
res.status(500).send('Internal Server Error');
}
});The StyleSheetManager component provides configuration and context for style sheet behavior, including custom targets and server-side rendering settings.
declare const StyleSheetManager: React.ComponentType<{
sheet?: StyleSheet;
target?: HTMLElement;
children?: React.ReactNode;
disableCSSOMInjection?: boolean;
disableVendorPrefixes?: boolean;
stylisPlugins?: StylisPlugin[];
}>;
declare const StyleSheetContext: React.Context<IStyleSheetContext>;
declare const StyleSheetConsumer: React.Consumer<IStyleSheetContext>;
interface IStyleSheetContext {
styleSheet: StyleSheet;
stylis: Stringifier;
}Usage Examples:
import { StyleSheetManager } from 'styled-components';
function App() {
const [customTarget, setCustomTarget] = React.useState(null);
React.useEffect(() => {
// Create a custom target for styles (e.g., in iframe or shadow DOM)
const target = document.createElement('div');
document.head.appendChild(target);
setCustomTarget(target);
return () => {
document.head.removeChild(target);
};
}, []);
if (!customTarget) return null;
return (
<StyleSheetManager target={customTarget}>
<StyledComponents />
</StyleSheetManager>
);
}// Useful for certain SSR scenarios or when working with CSP
function ServerApp() {
return (
<StyleSheetManager disableCSSOMInjection>
<App />
</StyleSheetManager>
);
}import rtlPlugin from 'stylis-plugin-rtl';
function RTLApp() {
return (
<StyleSheetManager stylisPlugins={[rtlPlugin]}>
<App />
</StyleSheetManager>
);
}// utils/ssr.js
import { ServerStyleSheet } from 'styled-components';
export function extractStyles(app) {
const sheet = new ServerStyleSheet();
try {
const html = renderToString(sheet.collectStyles(app));
const styles = sheet.getStyleElement();
return { html, styles };
} finally {
sheet.seal();
}
}
// Error handling wrapper
export function safeExtractStyles(app) {
try {
return extractStyles(app);
} catch (error) {
console.error('Style extraction failed:', error);
// Return app without styles as fallback
return {
html: renderToString(app),
styles: []
};
}
}// client/hydration.js
import { hydrate } from 'react-dom';
function hydrateApp() {
const rootElement = document.getElementById('root');
if (rootElement.hasChildNodes()) {
// Server-rendered content exists, hydrate
hydrate(<App />, rootElement);
} else {
// No server-rendered content, render normally
render(<App />, rootElement);
}
}
// Wait for stylesheets to load before hydrating
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', hydrateApp);
} else {
hydrateApp();
}// Long-running server memory management
class StyleSheetPool {
private sheets: Map<string, ServerStyleSheet> = new Map();
getSheet(requestId: string): ServerStyleSheet {
if (!this.sheets.has(requestId)) {
this.sheets.set(requestId, new ServerStyleSheet());
}
return this.sheets.get(requestId);
}
releaseSheet(requestId: string): void {
const sheet = this.sheets.get(requestId);
if (sheet) {
sheet.seal();
this.sheets.delete(requestId);
}
}
cleanup(): void {
for (const [id, sheet] of this.sheets) {
sheet.seal();
}
this.sheets.clear();
}
}
const styleSheetPool = new StyleSheetPool();
// Middleware to manage sheets per request
app.use((req, res, next) => {
req.requestId = generateUniqueId();
res.on('finish', () => {
styleSheetPool.releaseSheet(req.requestId);
});
next();
});import { renderToPipeableStream } from 'react-dom/server';
import { ServerStyleSheet } from 'styled-components';
app.get('*', (req, res) => {
const sheet = new ServerStyleSheet();
let didError = false;
const stream = renderToPipeableStream(
sheet.collectStyles(<App />),
{
bootstrapScripts: ['/bundle.js'],
onShellReady() {
res.statusCode = didError ? 500 : 200;
res.setHeader('Content-type', 'text/html');
// Inject initial HTML with style placeholders
res.write(`
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<style id="styled-components-ssr"></style>
</head>
<body>
<div id="root">
`);
stream.pipe(res, { end: false });
},
onAllReady() {
// Inject collected styles
const styles = sheet.getStyleTags();
res.write(`
</div>
<script>
document.getElementById('styled-components-ssr').outerHTML = \`${styles}\`;
</script>
</body>
</html>
`);
res.end();
sheet.seal();
},
onError(err) {
didError = true;
console.error('SSR Error:', err);
},
}
);
setTimeout(() => {
if (!res.headersSent) {
stream.abort();
sheet.seal();
}
}, 10000); // 10 second timeout
});// client/index.js
import { hydrateRoot } from 'react-dom/client';
function initializeApp() {
const container = document.getElementById('root');
// Ensure styles are loaded before hydration
const styleTags = document.querySelectorAll('style[data-styled]');
if (styleTags.length > 0) {
// Server-side styles are present, safe to hydrate
hydrateRoot(container, <App />);
} else {
// No server styles, render client-only
const root = createRoot(container);
root.render(<App />);
}
}
// Check if document is ready
if (document.readyState !== 'loading') {
initializeApp();
} else {
document.addEventListener('DOMContentLoaded', initializeApp);
}// Wait for critical styles to load
function waitForStyles(): Promise<void> {
return new Promise((resolve) => {
const checkStyles = () => {
const styledTags = document.querySelectorAll('style[data-styled]');
const linkTags = document.querySelectorAll('link[rel="stylesheet"]');
let allLoaded = true;
linkTags.forEach(link => {
if (!link.sheet || link.sheet.cssRules.length === 0) {
allLoaded = false;
}
});
if (allLoaded && styledTags.length > 0) {
resolve();
} else {
requestAnimationFrame(checkStyles);
}
};
checkStyles();
});
}
// Usage
async function hydrateWithStyles() {
await waitForStyles();
hydrateRoot(document.getElementById('root'), <App />);
}// Extract styles for specific components
function extractComponentStyles(Component, props = {}) {
const sheet = new ServerStyleSheet();
try {
renderToString(sheet.collectStyles(<Component {...props} />));
return sheet.getStyleTags();
} finally {
sheet.seal();
}
}
// Pre-render critical components
const criticalStyles = [
extractComponentStyles(Header),
extractComponentStyles(Navigation),
extractComponentStyles(Footer),
].join('');function ConditionalSSRApp({ isBot }) {
if (isBot) {
// Full SSR for bots/crawlers
return (
<StyleSheetManager disableCSSOMInjection>
<App />
</StyleSheetManager>
);
}
// Client-side rendering for users
return <App />;
}
// Server route
app.get('*', (req, res) => {
const isBot = /bot|crawler|spider/i.test(req.get('User-Agent'));
if (isBot) {
// Full SSR
const { html, styles } = extractStyles(<ConditionalSSRApp isBot={true} />);
res.send(createHTML(html, styles));
} else {
// SPA shell
res.send(createSPAShell());
}
});Internal contexts and hooks for advanced styled-components integration and custom SSR scenarios.
// StyleSheet context interface
interface IStyleSheetContext {
shouldForwardProp?: ShouldForwardProp<'web'> | undefined;
styleSheet: StyleSheet;
stylis: Stringifier;
}
// StyleSheet context and consumer
declare const StyleSheetContext: React.Context<IStyleSheetContext>;
declare const StyleSheetConsumer: React.Consumer<IStyleSheetContext>;
// Hook for accessing StyleSheet context
function useStyleSheetContext(): IStyleSheetContext;
// Stylis context for CSS processing
type IStylisContext = Stringifier | void;
declare const StylisContext: React.Context<IStylisContext>;
declare const StylisConsumer: React.Consumer<IStylisContext>;Usage Examples:
import { useStyleSheetContext, StyleSheetContext } from 'styled-components';
// Custom hook for accessing stylesheet information
function useStyledContext() {
const context = useStyleSheetContext();
return {
styleSheet: context.styleSheet,
shouldForwardProp: context.shouldForwardProp,
stylis: context.stylis,
};
}
// Advanced SSR component with context access
function AdvancedSSRComponent() {
const { styleSheet, stylis } = useStyleSheetContext();
// Access to the underlying StyleSheet instance
const registeredStyles = styleSheet.getIds();
return (
<div data-styled-ids={registeredStyles.join(',')}>
Component content
</div>
);
}
// Custom StyleSheetManager wrapper
function CustomStyleSheetProvider({ children, customStylis }) {
return (
<StyleSheetContext.Provider value={{
styleSheet: new StyleSheet(),
stylis: customStylis,
shouldForwardProp: undefined,
}}>
{children}
</StyleSheetContext.Provider>
);
}