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 inNext.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 theDocument
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!