This article demonstrates the bare minimum difference on build size when using a css-in-js library & it's performance implications.
Ever wondered how your styles make it to the browser when using CSS-in-JS library like styled-components or emotion?
When we're defining our styles, we're actually creating a normal React component that has the styles attached to it. This means we are shipping the styles in a .js file rather than a .css file.
We're going to take a look at build sizes for the two most used CSS-in-JS libraries: emotion.js vs styled-components.
Apart from page rendering performance, build sizes directly affect the load time. Shipping large builds will naturally have lower load times & an increase in site's time to interactive. If the website relies on traffic from organic search and PPC campaigns, page speed is an important factor.
- Page load time and crawl budget rank will be the most important SEO indicators in 2020
- Site Speed Affects Adwords Pricing
In my setup, I spin up a Next.js boilerplate. We can do this by running
$ npx create-next-app
OR
$ yarn create next-app
Boilerplate build
Now, without doing any further changes, Let’s create a boilerplate production build.
We have 61.1 kB of FIRST LOAD JS.
Plot
In this assessment, We only add a styled header component which we create in the component.
mycomponent/styles.js
export const Header = styled.h1`
color: blue;
`;
mycomponent/index.js
import { Header } from './styles';
const MyComponent = () => <Header>Styling this component with emotion-js</Header>;
export default MyComponent;
Versions used:
- "react": "16.13.1"
- "@emotion/core": "10.0.35"
- "next": "9.5.2"
- "styled-components": "5.1.1"
Let’s go!
First up - styled-components
We have a 20% increase in our build size right away.
One can argue that a 13kB increase does not make any difference, however, in page speed performance - milliseconds matter & so do the amount of bytes we ship across the network.
Let’s take both builds on a test drive and deploy them on Vercel.
After deploying, I ran a page speed comparison on https://developers.google.com/speed/pagespeed/insights/
Results:
19% difference in Time to interactive
28% difference in First contentful paint
And 2 points skimmed off from the page speed score
Apart from the build sizes, the other performance hit is “rendering“ & “react re-renders“. The CSS-in-JS libraries depend on a runtime that helps them dynamically update the styles of a component. The CSS-in-JS libraries don’t create CSS classes at build-time, but instead dynamically generate and update style tags in the document whenever a component mounts and/or has it's props changed, making it favourable for theming & complex use of css with React.
If there’s such a difference in the tiniest example possible, an even more complex app can have heavier build sizes. Also, since we’re shipping our styles in a javascript file, it’s evident that increasing the number of styled components will increase the build size and hence reduce the page speed.
Next up - Emotion.js
Emotion.js performs slightly better than styled-components.
Here’s the Page speed comparison for both the libraries -
In terms of build size, we can see emotion js stands somewhere midway between a plain boilerplate and styled-components.
After working with both the libraries extensively, I did not find a big difference in the JS APIs. There wasn't much difference in developer experience (DX) as well. If you’re using an older version of styled-components, your build sizes will tend to be much larger. Lately, the styled-components team have bridged the gap by reducing their build sizes.
If your project doesn’t handle theming or complex css, linaria can be a suitable option as it compiles js into css at build time.
Given that we know the how these libraries perform with build sizes, It will be interesting to see which one is faster when the styles mount (renders). Faster rendering on browsers will give us a lower “time to interactive“. This should be an interesting case study that demands an article of its own.
Top comments (5)
Great article! Is the data you get under "First Load JS" specific to Next? I'm using Gatsby and on executing a build I only get times, not sizes (see attachment). How do I get something like yours? Not sure if I am missing something obvious, but please enlighten me :)
Hi Timothy! Thanks for appreciating. Hoping the write up has helped you in some way. :)
For Gatsby, yes there is a webpack plugin which you need to configure in your gatsby-config.js file. The plugin goes by the name: gatsby-plugin-webpack-size
Great I'll try it!👌
Great content.
get info , was worth the time