Server-side rendered styled-components with Nextjs

Out of the box, Next.js comes configured with styled-jsx. But what if you want to use something else for styling?

I’ve recently worked on setting up Next.js with styled-components, and while it all went rather smoothly, I didn’t really understand what was going on 🙂 And if my setup was correct.

From the docs, it seems like the setup is straightforward:

Next.js docs

To use more sophisticated CSS-in-JS solutions, you typically have to implement style flushing for server-side rendering. We enable this by allowing you to define your own custom [<Document>](https://github.com/zeit/next.js/#user-content-custom-document) component that wraps each page.
https://github.com/zeit/next.js/#css-in-js

Styled components docs

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 .
You'll also need to customize the .babelrc and use babel-plugin-styled-components.
Refer to our example in the Next.js repo for an up-to-date usage example.
https://www.styled-components.com/docs/advanced#nextjs

Copy-pasting the solution as they describe works like a charm. But what does it do?!

How a page is rendered in Next.js

In Next.js, every page is declared in the pages directory and exports a component.
For example, say we have an About page:

// pages/about.js
export default () => (<div>Nice to meet you!</div>)

What Next.js does with the output of this is:

  • wraps it in a Document component that sets up the <html>, <body>, <head> etc.
  • runs it through a renderPage method that will synchronously render it on the server side

You can override the default Document by adding a _document.js file in your pages folder.

Pages in Next.js skip the definition of the surrounding document's markup. For example, you never include <html>, <body>, etc.
To override that default behavior, you must create a file at ./pages/_document.js, where you can extend the Document class.
https://github.com/zeit/next.js/#custom-document
renderPage (Function) a callback that executes the actual React rendering logic (synchronously).
It's useful to decorate this function in order to support server-rendering wrappers like Aphrodite's renderStatic
https://github.com/zeit/next.js/#custom-document

The custom _document.js file

This is how a custom _document.js would look like, if we just rendered the page and nothing else:

import Document, { Head, Main, NextScript } from 'next/document'

export default class MyDocument extends Document {
  static getInitialProps ({ renderPage }) {
    // Returns an object like: { html, head, errorHtml, chunks, styles }     
    return renderPage();
  }

  render () {    
    return (
      <html>
        <Head>
          <title>My page</title>
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

Server-side rendering the styles

To enable server side rendering of the styles, we will need to do two things:

  • hook into the renderPage method to parse the child component's styles server-side, on the initial page load
  • hook into the Document, so we can update the <head> with the <style> tags that are required by the components

So, here's a custom _document.js that just has comments where our code needs to go in:

import Document, { Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static getInitialProps ({ renderPage }) {
    const transform = (App) => {
        // Next.js gives us a `transformPage` function 
        // to be able to hook into the rendering of a page
        // Step 1: Here we will generate the styles
        return App;
    }
    const page = renderPage(transform);
    return { ...page };
  }

  render () {    
    return (
      <html>
        <Head>
          <title>My page</title>
          // Step 2: Here we will inject the styles into the page
          // through one or more <style/> tags 
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

Adding styled-components

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.
https://www.styled-components.com/docs/advanced#server-side-rendering

What this means is that to set up server side rendering of styled-components we need to:

  • extract the styles from the components into a <style> tag
  • add the <style> tag to the head of the page

Extracting the styles from the components into a style tag

The easiest way to understand this is to just look at the code.
Here's an updated getInitalProps method with code for instantiating the ServerStyleSheet class of styled-components.

  static getInitialProps ({ renderPage }) {
    // Step 1: Create an instance of ServerStyleSheet
    const sheet = new ServerStyleSheet()
    const transform = (App) => {
      // Step 2: Retrieve styles from components in the page
      return sheet.collectStyles(<App />);      
      // Same as:
      // return <StyleSheetManager sheet={sheet.instance}>
      //    <App/>
      // </StyleSheetManager>
    }
    // Step 3: Extract the styles as <style> tags
    const styleTags = sheet.getStyleElement()
    const page = renderPage(transform);
    // Step 4: Pass it on as a prop
    return { ...page, styleTags };
  }

Adding the <style> tag to the <head> of the page

render () {
    return (
      <html>
        <Head>
          <title>My page</title>
          /* Just one step: output the styles in the head */
          {this.props.styleTags}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }

Final _document.js file

We can now put all the pieces together.
In this example the code is more succint (for example, the code defining transform as a separate variable is skipped).

import Document, { Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static getInitialProps ({ renderPage }) {
    const sheet = new ServerStyleSheet()
    const page = renderPage(App => props => sheet.collectStyles(<App {...props} />))
    const styleTags = sheet.getStyleElement()
    return { ...page, styleTags }
  }

  render () {
    return (
      <html>
        <Head>
          <title>My page</title>
          {this.props.styleTags}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    )
  }
}

If you run the application at this point, it will work, however it’s likely you will see weird bugs related to styling from time to time.
That’s because styled-components needs an extra step to make sure the css class names are kept the same on the client side and server side.

Which brings us to the babel plugin.

Installing the styled components babel plugin

By adding a unique identifier to every styled component this plugin avoids checksum mismatches due to different class generation on the client and on the server.
If you do not use this plugin and try to server-side render styled-components React will complain.
https://www.styled-components.com/docs/tooling#serverside-rendering

Install plugin

npm install --save-dev babel-plugin-styled-components

Configure

You will need to create a custom .babelrc file and and enable the plugin there.

Step 1. Create the .babelrc file (if it doesn’t already exist)
This needs to be in the root of the project.

Step 2: Add babel/preset

"presets": [
  "next/babel"
]

Not sure why, but their docs say it should be added and all examples include it :)

Most of the time, when writing a custom .babelrc file, you need to add next/babel as a preset.
https://github.com/zeit/next.js/tree/canary/examples/with-custom-babel-config

Step 3: Add styled components plugin

  • enable the ssr flag
  • note: displayName will generate class names that are easier to debug (will contain also the component name instead of just hashes); preprocess - experimental feature turned off explicitly
"plugins": [
    ["styled-components", { "ssr": true, "displayName": true, "preprocess": false } ]
]

Final .babelrc file

// .babelrc
{
    "presets": [
        "next/babel"
    ],
    "plugins": [
        ["styled-components", { "ssr": true, "displayName": true, "preprocess": false } ]
    ]
}

Summary

Whoooa, quite a lot of steps, right?
But if you look closely, you'll see it's actually just a lot of things to wrap your head around all at once.

The steps themselves are just four:

  1. Install styled-components
  2. Create the custom _document.js file (you can copy the contents from the official example code)
  3. Install styled components Babel plugin
  4. Create the .babelrc file (you can copy the contents from the official example code)

Use this checklist when you're going to set this up yourself and let me know in the comments below how it worked for you!