v3.1.0: A massive performance boost and streaming server-side rendering support

A new CSS injection mechanism means faster client-side rendering in production 🔥 and streaming server-side rendering support enables a faster time-to-first-byte! 🔥🔥

Faster CSS injection in production

This patch has been a long time coming and has a long history. Almost one and a half years ago (!) Sunil Pai found a new and widely unknown DOM API: insertRule. It allows one to insert CSS from JavaScript into the DOM at blazing speed; the only downside being that the styles aren’t editable from browser DevTools.

When Glen and Max first built styled-components, they were 100% focused on the developer experience. Performance problems were sparse for smaller applications, so they decided against using insertRule. As adoption grew and people used styled-components for larger apps, style injection turned out to be a bottleneck for folks with highly dynamic use cases.

Thanks to Ryan Schwers, a frontend engineer at Reddit, styled-components v3.1.0 now uses insertRule in production by default.

We ran some benchmarks against the previous version (v3.0.2) and the new one with insertRule, and the results were even better than our (already high) expectations:

Initial mount time of the benchmark app was reduced by ~10x, and re-render time was reduced by ~20x!

Note that the benchmarks are stress testing the library, and are not representative of a real application. While your app will (probably) not mount 10x faster, Time-To-First-Interactive dropped by hundreds of milliseconds in one of our production applications!

Here’s how styled-components holds up in comparison to other major React CSS-in-JS frameworks in those benchmarks:

styled-components compared to all other major React CSS-in-JS frameworks. (light red: v3.0.2; dark red: v3.1.0)

While it’s not (yet) the fastest CSS-in-JS framework in micro-benchmarks, it’s only marginally slower than the fastest ones — to the point where it no longer could be considered a bottleneck. The real-world results are highly encouraging and we can’t wait for you all to report back with your findings!

Streaming server-side rendering

Streaming server-side rendering was introduced in React v16. It allows the application server to send HTML as it becomes available while React is still rendering, which makes for a faster Time-To-First-Byte (TTFB) and allows your Node server to handle back-pressure more easily.

That doesn’t play well with CSS-in-JS: Traditionally, we inject a <style> tag with all your components’ styles into the <head> after React finishes rendering. However, in the case of streaming, the <head> is sent to the user before any components have been rendered, so we can’t inject into it anymore.

The solution is to interleave the HTML with <style> blocks as components are rendered, rather than waiting until the very end and injecting all the components at once. Because that messes with ReactDOM on the client (HTML being present that React wasn’t responsible for), we have to consolidate all those style tags back into the <head> before rehydration.

We’ve implemented exactly that; you can now use streaming server-side rendering with styled-components! Here’s how:

import { renderToNodeStream } from 'react-dom/server'
import styled, { ServerStyleSheet } from 'styled-components'
res.write('<!DOCTYPE html><html><head><title>My Title</title></head><body><div id="root">')
const sheet = new ServerStyleSheet()
const jsx = sheet.collectStyles(<App />)
// Interleave the HTML stream with <style> tags
const stream = sheet.interleaveWithNodeStream(
stream.pipe(res, { end: false })
stream.on('end', () => res.end('</div></body></html>'))

Later on client-side, the consolidateStreamedStyles() API must be called to prepare for React’s rehydration phase:

import ReactDOM from 'react-dom'
import { consolidateStreamedStyles } from 'styled-components'
/* Make sure you call this before ReactDOM.hydrate! */
ReactDOM.hydrate(<App />, rootElem)

That’s all there is to it! 💯 (check out the streaming docs for more information)

v3: no breaking changes

Good news! If you’re on v2 (or even v1), the new version is backward-compatible and should be a seamless upgrade. Dozens of improvements have made their way into these new versions, so please take a look and we hope you and your visitors enjoy them!

See the changelog for more information about both the v3.0.0 and the v3.1.0 release.

Stay stylish! 💅

Discuss this post in the styled-components community.

Thanks to Gregory Shehet for his CSS-in-JS benchmarks, which are referenced throughout this post.