Back to Styled Components

Server Side Rendering

sections/advanced/server-side-rendering.mdx

latest8.2 KB
Original Source

Server Side Rendering | v2+

styled-components supports concurrent server side rendering, with stylesheet rehydration. The basic idea is that everytime you render your app on the server, you can create a ServerStyleSheet and add a provider to your React tree, that accepts styles via a context API.

This doesn't interfere with global styles, such as keyframes or createGlobalStyle and allows you to use styled-components with React DOM's various SSR APIs.

React Server Components | v6.3.0+

styled-components now natively supports React Server Components (RSC) with zero configuration. In RSC environments, styled components automatically emit inline <style> tags that React 19 hoists and deduplicates.

Key behaviors in RSC:

  • No 'use client' directive required
  • ThemeProvider and StyleSheetManager become pass-through components (no-ops)
  • CSS is automatically injected inline

Best practices for RSC:

DoDon't
Use static stylesUse dynamic interpolations (serialization overhead)
Use data attributes for variants (&[data-size='lg'])Use props for discrete style variants
Use CSS custom properties via inline style propRely on ThemeProvider (no-op in RSC)
Use build-time CSS variable generation for themingUse runtime theme context

Theming with CSS custom properties:

Since ThemeProvider is a no-op in RSC, use CSS custom properties for theming instead. Variables set on a parent element cascade to all DOM children:

tsx
const Container = styled.div``;
const Button = styled.button`
  background: var(--color-primary, blue);
`;

<Container style={{ '--color-primary': 'orchid' }}>
  <Button>Inherits orchid background</Button>
</Container>

Tooling setup

In order to reliably perform server side rendering and have the client side bundle pick up without issues, you'll need to use our babel plugin. It prevents checksum mismatches by adding a deterministic ID to each styled component. Refer to the tooling documentation for more information.

For TypeScript users, our resident TS guru Igor Oleinikov put together a TypeScript plugin for the webpack ts-loader / awesome-typescript-loader toolchain that accomplishes some similar tasks.

If possible, we definitely recommend using the babel plugin though because it is updated the most frequently. It's now possible to compile TypeScript using Babel, so it may be worth switching off TS loader and onto a pure Babel implementation to reap the ecosystem benefits.

Example

The basic API goes as follows:

tsx
import { renderToString } from 'react-dom/server';
import { ServerStyleSheet } from 'styled-components';

const sheet = new ServerStyleSheet();
try {
  const html = renderToString(sheet.collectStyles(<YourApp />));
  const styleTags = sheet.getStyleTags(); // or sheet.getStyleElement();
} catch (error) {
  // handle error
  console.error(error);
} finally {
  sheet.seal();
}

The collectStyles method wraps your element in a provider. Optionally you can use the StyleSheetManager provider directly, instead of this method. Just make sure not to use it on the client-side.

tsx
import { renderToString } from 'react-dom/server';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';

const sheet = new ServerStyleSheet();
try {
  const html = renderToString(
    <StyleSheetManager sheet={sheet.instance}>
      <YourApp />
    </StyleSheetManager>
  );
  const styleTags = sheet.getStyleTags(); // or sheet.getStyleElement();
} catch (error) {
  // handle error
  console.error(error);
} finally {
  sheet.seal();
}

The sheet.getStyleTags() returns a string of multiple <style> tags. You need to take this into account when adding the CSS string to your HTML output.

Alternatively the ServerStyleSheet instance also has a getStyleElement() method that returns an array of React elements.

If rendering fails for any reason it's a good idea to use try...catch...finally to ensure that the sheet object will always be available for garbage collection. Make sure sheet.seal() is only called after sheet.getStyleTags() or sheet.getStyleElement() have been called otherwise a different error will be thrown.

sheet.getStyleTags() and sheet.getStyleElement() can only be called after your element is rendered. As a result, components from sheet.getStyleElement() cannot be combined with <YourApp /> into a larger component.

Next.js

With Babel

Basically you need to add a custom pages/_document.js (if you don't have one). Then copy the logic for styled-components to inject the server side rendered styles into the <head>.

Refer to our example in the Next.js repo for an up-to-date usage example.

With SWC

Since version 12, Next.js uses a Rust compiler called SWC. If you're not using any babel plugin, you should refer to this example instead.

On this version, you only need to add styledComponents: true, at the compiler options in the next.config.js file and modify _document file with getInitialProps as in this example to support SSR.

App directory

With styled-components v6.3.0+: RSC is natively supported with zero configuration. Styled components work in Server Components without a 'use client' directive or registry setup. See the React Server Components section above for best practices.

With styled-components v6.0–6.2: You'll need to put a styled-components registry in one of your layout files, as described in Next.js docs. Note that the 'use client' directive is required, so styled-components will appear in your client bundle.

Gatsby

Gatsby has an official plugin that enables server-side rendering for styled-components.

Refer to the plugin's page for setup and usage instructions.

Streaming Rendering

styled-components offers a streaming API for use with ReactDOMServer streaming methods.

React 18+: renderToNodeStream is deprecated. Use renderToPipeableStream instead for modern streaming SSR.

There are two parts to a streaming implementation:

On the server:

ReactDOMServer.renderToNodeStream emits a "readable" stream that styled-components wraps. As whole chunks of HTML are pushed onto the stream, if any corresponding styles are ready to be rendered, a style block is prepended to React's HTML and forwarded on to the client browser.

tsx
import { renderToNodeStream } from 'react-dom/server';
import styled, { ServerStyleSheet } from 'styled-components';

// if you're using express.js, you'd have access to the response object "res"

// typically you'd want to write some preliminary HTML, since React doesn't handle this
res.write('<html><head><title>Test</title></head><body>');

const Heading = styled.h1`
  color: red;
`;

const sheet = new ServerStyleSheet();
const jsx = sheet.collectStyles(<Heading>Hello SSR!</Heading>);
const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx));

// you'd then pipe the stream into the response object until it's done
stream.pipe(res, { end: false });

// and finalize the response with closing HTML
stream.on('end', () => res.end('</body></html>'));

On the client:

tsx
import { hydrate } from 'react-dom';

hydrate();
// your client-side react implementation

After client-side rehydration is complete, styled-components will take over as usual and inject any further dynamic styles after the relocated streaming ones.