Logo

Configuring Emotion for SSR in Next.js

css in js

It's easy to forget that global styles like background and font color should be server-rendered — otherwise, there's a flash of unstyled content.

Install necessary packages

1npm install @emotion/react @emotion/server @emotion/styled

Configure _document.tsx`

This file controls the HTML document structure on the server. Gotta plug in Emotion here so the critical CSS is sent in the initial HTML.

1import React from 'react'
2
3import { cache } from '@emotion/css'
4import { CacheProvider } from '@emotion/react'
5import createEmotionServer from '@emotion/server/create-instance'
6import Document, {
7 Html,
8 Head,
9 Main,
10 NextScript,
11 DocumentContext,
12} from 'next/document'
13
14export default class MyDocument extends Document {
15 static async getInitialProps(ctx: DocumentContext) {
16 const originalRenderPage = ctx.renderPage
17 const { extractCriticalToChunks } = createEmotionServer(cache)
18
19 ctx.renderPage = () =>
20 originalRenderPage({
21 enhanceApp: (App) => (props) => (
22 <CacheProvider value={cache}>
23 <App {...props} />
24 </CacheProvider>
25 ),
26 })
27
28 const initialProps = await Document.getInitialProps(ctx)
29 const emotionStyles = extractCriticalToChunks(initialProps.html)
30 const emotionStyleTags = emotionStyles.styles.map((style) => (
31 <style
32 data-emotion={`${style.key} ${style.ids.join(' ')}`}
33 key={style.key}
34 dangerouslySetInnerHTML={{ __html: style.css }}
35 />
36 ))
37
38 return {
39 ...initialProps,
40 styles: [
41 ...React.Children.toArray(initialProps.styles),
42 ...emotionStyleTags,
43 ],
44 }
45 }
46
47 render() {
48 return (
49 <Html lang="en">
50 <Head />
51 <body>
52 <Main />
53 <NextScript />
54 </body>
55 </Html>
56 )
57 }
58}

_app.tsx

The ThemeProvider needs to go here to provide styling context, and the CacheProvider helps match the setup from _document.

1import { ThemeProvider, CacheProvider } from '@emotion/react'
2import { cache } from '@emotion/css'
3
4const theme = {
5 colors: {
6 primary: '#0070f3',
7 },
8}
9
10function MyApp({ Component, pageProps }) {
11 return (
12 <CacheProvider value={cache}>
13 <ThemeProvider theme={theme}>
14 <Component {...pageProps} />
15 </ThemeProvider>
16 </CacheProvider>
17 )
18}
19
20export default MyApp

Done!

There should be two style tags with data-emotion attributes in <head>

One is injected during SSR: <style data-emotion="css-global">

emotion-style-tag-ssr

The other shows up during CSR after hydration

emotion-style-tag

Now global styles render immediately on first load. No more flashes of plain white before the theme kicks in.