This week let’s talk about the critical rendering path and how to avoid render-blocking CSS. That is, CSS which can prevent the browser rendering any processed content until the CSSOM has been fully constructed.
There are many articles and presentations about inlining critical CSS, so I won’t walk through the process step-by-step. Filament Group has provided a clear, comprehensive how-to on making RWD fast, which includes an excellent explanation of critical CSS. Once you’re comfortable creating your own inline CSS to appease the PageSpeed gods, I want to bring up an important issue to watch out for: inline CSS that blocks rendering.
Inline but blocking
What?! you must be asking. Isn’t inline CSS faster because there’s no external request for the styles? That is correct for the most part. Still, it’s possible for the CSS to block rendering if it contains external requests. The following demo page contains nothing but a few paragraphs and inline CSS. Weighing in at 3.3KB I’d expect it to load in the first packet response. Chances are if you load it on a mobile device, the page will be white for about 5 seconds before rendering the page:
This might not be a problem if you’re manually authoring critical CSS. But not everyone uses that approach, which can be somewhat time-consuming. That’s why folks are often using tools like critical or grunt-critical to automatically generate the CSS. Not only can they test multiple viewport sizes to calculate the styles, but they can even inline it for you! I prefer these tools because they’re pretty darn convenient, but they also have the potential to cause trouble.
Automatic isn’t always perfect
The advantage we get by automating tasks is enormous. For some tasks, like concatenation, you could say it’s downright foolish to do by hand what a computer can do a million times as fast. We can safely hand the task off to the computer and actually expect it to be done better and faster than a human can.
Producing CSS for the critical rendering path, on the other hand, is a matter of balancing several important factors. A computer can’t always make the best decision. Do you optimize for a mobile viewport, a desktop-sized viewport, or try to find a “sweet spot” that works for both? How “complete” do you want the first render to look? All of these questions have to be answered within a budget of just a few KB of CSS.
For example, if you are applying a web font to your page titles, and it is above the fold. The tool will probably identify the
@font-face rule and include it in your critical CSS. But that creates an external request! Same for background images and icon fonts, all commonly used near the top of a web page to enhance logos, titles, menus and so forth.
The makers of these tools are aware of the risks. In fact, Critical has an open GitHub issue requesting excludes by file/selector. But for now it’s up to your team to be vigilant and ensure that the CSS is actually non-blocking.
Update 2015-06-12: the issue noted above has now been resolved! In the latest version of
critical you are able to filter out blocks and properties of your choosing. Thanks to Ben Zörb for his work on this issue.
Test your work
Alright so here we are. We have great tools that automate a tricky task, and for the most part they get it right. But when they get it wrong, it’s high impact. We know exactly what will cause problems, which sounds like a perfect situation to implement a test!
To avoid shipping inline styles with external requests, use a test to check CSS for known properties that block rendering.
#!/bin/bash # The commands look for 'font-face' or 'url' within the # critical CSS. Both properties will cause the browser # to make an HTTP request, but since the critical CSS is # inline, it should avoid making external requests. # # External requests within CSS defeat the purpose of # inlining in your HTML since they incur the same type # of network delay that external CSS would cause. FACE=`grep -c 'font-face' path/to/critical.css` URL=`grep -c 'url(' path/to/critical.css` if [ "$FACE" == "0" ] && [ "$URL" == "0" ]; then echo "Critical: ✔︎ Yay! The generated CSS makes zero external requests." exit 0 else TOTAL=$(($FACE + $URL)) echo "Critical: ✘ Rats! The generated CSS makes $TOTAL external requests." exit $TOTAL fi
When the test fails, you can go in and manually tweak your styles, removing the bits that cause delays. This script is just to demonstrate the concept, and is extremely simple. It only tests for two strings,
url. Depending on your CSS, the test might have to be more complex. For instance, a
url() containing a data URI is not a problem, and should pass. Or you might want to check for
@import. If you have other good testing suggestions, leave them in the comments!
Finally, for team-wide coverage add this test as a pre-commit hook in your VCS so people who work with CSS will have to verify the changes before merging or shipping.
Want to learn more?
Check out our open source repository of frontend performance training materials, which includes this test amongst many others!
Credit and thanks go to Addy Osmani for fact-checking this post.