Server Side Rendering

✏️ Edit this page

Server side rendering works out of the box in Emotion 10 and above if you’re only using @emotion/core and @emotion/styled. This means you can call React’s renderToString or renderToNodeStream methods directly without any extra configuration.

import { renderToString } from 'react-dom/server'
import App from './App'

let html = renderToString(<App />)

Using Emotion 10 with the old SSR APIs

It’s also possible to use emotion 10 with the SSR APIs for vanilla Emotion. It should only be used for compatibility and migration purposes.

import { renderStylesToString } from 'emotion-server'
import { cache } from 'emotion'
import { CacheProvider } from '@emotion/core'
import { renderToString } from 'react-dom/server'

let element = (
  <CacheProvider value={cache}>
    <App />

let html = renderStylesToString(renderToString(element))



This returns a string of html that inlines the critical css required right before it’s used.

import { renderToString } from 'react-dom/server'
import { renderStylesToString } from 'emotion-server'
import App from './App'

const html = renderStylesToString(renderToString(<App />))


This returns a Node Stream Writable that can be used to insert critical css right before it’s required. This can be used with React’s streaming API.

import { renderToNodeStream } from 'react-dom/server'
import { renderStylesToNodeStream } from 'emotion-server'
import App from './App'

const stream = renderToNodeStream(<App />).pipe(


This returns an object with the properties html, ids and css. It removes unused rules that were created with emotion(it still includes rules that were inserted with injectGlobal).

import { renderToString } from 'react-dom/server'
import { extractCritical } from 'emotion-server'
import App from './App'

const { html, ids, css } = extractCritical(
  renderToString(<App />)


hydrate should be called on the client with the ids that extractCritical returns. If you don’t call it then emotion will reinsert all the rules. hydrate is only required for extractCritical, not for renderStylesToString or renderStylesToNodeStream, hydration occurs automatically with renderStylesToString and renderStylesToNodeStream.

import { hydrate } from 'emotion'



To use emotion’s SSR with Next.js you need a custom Document component in pages/_document.js that renders the styles and inserts them into the <head>. An example of Next.js with emotion can be found in the Next.js repo.


This only applies if you’re using vanilla Emotion or a version of Emotion prior to v10. For v10 and above, SSR just works in Next.js.


To use emotion’s SSR with Gatsby, you can use gatsby-plugin-emotion or you can do it yourself with emotion and Gatsby’s various APIs but it’s generally recommended to use gatsby-plugin-emotion. There’s an example available in the Gatsby repo or you can look at this site which is built with Gatsby!

yarn add gatsby-plugin-emotion


module.exports = {
  plugins: [...otherGatsbyPlugins, 'gatsby-plugin-emotion']


While Emotion 10 and above supports SSR out of the box, it’s still recommended to use gatsby-plugin-emotion as gatsby-plugin-emotion will enable babel-plugin-emotion and other potential future optimisations.


If you are using Puppeteer to prerender your application, emotion’s speedy option has to be disabled so that the CSS is rendered into the DOM.


// This has to be run before emotion inserts any styles so it's imported before the App component
import './disable-speedy'
import ReactDOM from 'react-dom'
import App from './App'

const root = document.getElementById('root')

// Check if the root node has any children to detect if the app has been prerendered
if (root.hasChildNodes()) {
  ReactDOM.hydrate(<App />, root)
} else {
  ReactDOM.render(<App />, root)


import { sheet } from 'emotion'

// Check if the root node has any children to detect if the app has been preprendered
// speedy is disabled when the app is being prerendered so that styles render into the DOM
// speedy is significantly faster though so it should only be disabled during prerendering
if (!document.getElementById('root').hasChildNodes()) {


The sheet.speedy call has to be run before anything that inserts styles so it has to be put into it’s own file that’s imported before anything else.