Codementor Events

Server-side rendered styled-components with Nextjs

Published May 19, 2018Last updated Nov 15, 2018

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 <head>.
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:

[ ] Install styled-components

[ ] Create the custom _document.js file (you can copy the contents from the official example code)

[ ] Install styled components Babel plugin

[ ] 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!

This post was originally published on my blog at jsramblings.com

Discover and read more posts from Corina
get started
post comments3Replies
hannah morison
a year ago

can you please help me out to make a custom site example pages are: https://tattoovillas.com/how-long-to-use-aquaphor-on-tattoo/
please response me if its relevent to your expertise.
thankyou

DAVID JOHNSON
2 years ago

The code you shared is a guide to setting up server-side rendering of styled-components in a Next.js application. Let’s break down what each part does:

  1. Custom _document.js:

    • In a Next.js application, the _document.js file allows you to customize the HTML document that wraps each page.
    • By creating this file in the pages directory, you can extend the Document class provided by Next.js.
    • The getInitialProps method is a static method that runs on the server to generate the initial props for the document.
    • In the provided example, the getInitialProps method is extended to include styled-components related logic.
  2. Server-side rendering the styles:

    • To enable server-side rendering of styled-components, you need to perform two main steps.
    • First, you create an instance of ServerStyleSheet from styled-components.
    • Then, you define a function (transform) that wraps the App component and collects the styles using sheet.collectStyles(<App />).
    • This ensures that the styles used in the components are extracted and available for server rendering.
    • After rendering the page using the renderPage method, you can retrieve the style tags using sheet.getStyleElement().
  3. Adding the <style> tag to the <head>:

    • In the render method of the custom _document.js, you can access the styleTags prop obtained from getInitialProps.
    • By placing {this.props.styleTags} within the Head component, the style tags are injected into the HTML head section of the page.
    • This ensures that the styles collected from the components are properly applied.
  4. Babel plugin:

    • To avoid checksum mismatches between server-side and client-side rendering, you need to install and configure the babel-plugin-styled-components.
    • Create a .babelrc file in the project’s root directory if it doesn’t exist already.
    • In the .babelrc file, add the plugin configuration for styled-components with the ssr flag set to true.
    • Additionally, you can enable the displayName option to generate class names that are easier to debug.

The combination of the custom _document.js, server-side rendering logic, and the babel-plugin-styled-components configuration allows Next.js to properly render and apply styled-components styles on the server side.

Remember to install the necessary dependencies, including styled-components and babel-plugin-styled-components, as mentioned in the guide.

Note: The code provided assumes a basic understanding of Next.js and styled-components. It’s recommended to refer to the official documentation for detailed explanations and up-to-date usage examples.

Regards;
David Johnson.

deutscheclub
a year ago

REALLY INFORMATIVE GONNA USE THIS HERE https://deutscheclub.de/