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'23import { 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'1314export default class MyDocument extends Document {15 static async getInitialProps(ctx: DocumentContext) {16 const originalRenderPage = ctx.renderPage17 const { extractCriticalToChunks } = createEmotionServer(cache)1819 ctx.renderPage = () =>20 originalRenderPage({21 enhanceApp: (App) => (props) => (22 <CacheProvider value={cache}>23 <App {...props} />24 </CacheProvider>25 ),26 })2728 const initialProps = await Document.getInitialProps(ctx)29 const emotionStyles = extractCriticalToChunks(initialProps.html)30 const emotionStyleTags = emotionStyles.styles.map((style) => (31 <style32 data-emotion={`${style.key} ${style.ids.join(' ')}`}33 key={style.key}34 dangerouslySetInnerHTML={{ __html: style.css }}35 />36 ))3738 return {39 ...initialProps,40 styles: [41 ...React.Children.toArray(initialProps.styles),42 ...emotionStyleTags,43 ],44 }45 }4647 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'34const theme = {5 colors: {6 primary: '#0070f3',7 },8}910function MyApp({ Component, pageProps }) {11 return (12 <CacheProvider value={cache}>13 <ThemeProvider theme={theme}>14 <Component {...pageProps} />15 </ThemeProvider>16 </CacheProvider>17 )18}1920export 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">
The other shows up during CSR after hydration
Now global styles render immediately on first load. No more flashes of plain white before the theme kicks in.