CSS-in-JS can be a huge win for maintainability. But for large statically rendered websites, plain CSS still has its place.
More and more developers are switching to CSS-in-JS, including big names like Microsoft, Atlassian, and… the Eurovision song contest! And while I haven’t always been a huge fan of CSS-in-JS, even I’m coming around to its benefits:
It looks like CSS-in-JS will dominate the styling of web apps for the foreseeable future. But web apps only make up a fraction of the web, because content is still king — as the meteoric rise of Gatsby has made apparent.
As a React developer, there’s as good a chance as any that you’ll be working on statically rendered web sites. And as I discovered while building create-react-blog and Frontend Armory, using CSS-in-JS for statically rendered sites still comes with a few caveats…
Luckily, the teams maintaining styled-components and emotion allow you to solve this with a little extra code. For instance, styled-components provides the
ServerStyleSheet object, which allows you to statically render your styles at the same time as you statically render your HTML. Then, you just send the statically rendered
<style> tag as part of your HTML:
By pre-rendering a
<style> tag and sending it with your HTML, you’ll avoid the flash of unstyled content — but there’s a catch. As
ServerStyleSheet only produces styles for the initial props, any use of component state,
componentDidUpdate will not be reflected in the server rendered styles. Given that your pre-rendered HTML has the same constraint, this shouldn’t be a problem. But if you do need a little help fetching the initial data for each of your app’s URLs, take a look at Navi — a router that was built with static/server rendering in mind. But I digress.
Statically rendering your styles has another benefit: it reduces the amount of CSS that’s required on each page’s initial load. This is because the rendered
import(). This can be great for performance… or it can result in many megabytes of CSS that is invalidated on every update — even for updates that don’t touch the content.
If you take a look at the generated
<style> tag in the above example, you’ll notice that it has a
data-styled attribute. This is important, because it shows that styled-components is tied to that
<style> tag. You can’t reliably extract the contents of that
<style> tag into a CSS file referenced by
<link>. Which is… maybe not that much of a problem?
I mean, why would you want to put your styles in a separate file anyway?
<script> tag, and thus HTML file, in your app.
<!-- For example, this script tag: --> <script src="/static/js/runtime~main-47df755c101a4.js"></script> <!-- Might change to this after fixing a typo: --> <script src="/static/js/runtime~main-55ce84a0cc19b.js"></script>
For apps with few pages, little traffic, or small
<style> tags, this is a non-issue. But for content-focused websites, the numbers can start to add up. For example, say that you’re running a site with 1000 pages, and with 25kb of critical CSS on each. Across all your HTML files, you’ll now have 25mb of CSS in
<style> tags; and all of that CSS needs to be pushed to the CDN with every change to your site — even if your only change is to fix a typo!
Is it a problem to have to push all of your inline CSS to a CDN with every change? Is it a problem if users can’t cache the critical CSS? The answer is — of course — it depends. In particular, there are three conditions which can cause issues:
In particular, if your site meets all three of these conditions, then there’s a good chance that you can improve performance (and save on hosting costs) by moving some of your CSS out to separate CSS files. Keep in mind that you can continue using CSS-in-JS alongside plain CSS or CSS Modules — you’ll just want to keep the size of your critical CSS manageable.
Of course, styled-components isn’t the only kid on the block. The next most popular tool, emotion, has much the same story as styled-components. But there’s also linaria — a CSS-in-JS tool that is more focused towards static rendering. If you want to use CSS-in-JS, but styled-components doesn’t suit your requirements, then linaria is definitely worth checking out!
But maybe your heart is set on styled-components — after all, it’s got an all-star team and a huge community. And importantly, it’s open source, so you can help out! The styled-components team is currently working to make it possible to extract cacheable CSS — so if you’d like to chip in, take a look at this Pull Request.
Lastly, it must be said that while CSS-in-JS is a great option, it’s not a necessity. CSS Modules and SASS solve most of the same issues while working out of the box with create-react-app and create-react-blog. Both CSS-in-JS and plain CSS have their place, and knowing the ins and outs of both will help you pick the right tool for the job.
Have you noticed how ridiculously fast Frontend Armory is? While building it, I’ve spent gobs of time learning the ins and outs of static rendering and code splitting, and now I want to save you the trouble of doing the same. So. If you’re building a React app and speed affects your bottom line (or you just want a snappy UI), then you need to see my upcoming course: React in Practice. More details are coming soon; join Frontend Armory now to be the first to find out — it’s free!