"When I was younger, so much younger than today" and upset and full of vinegar about the state of the world, I'd say things like "CSS is the worst" (not really). Now, half a year later, older and wiser and more accepting, I'd agree to mellow down to "CSS is render-blocking".
Un-render-blocking CSS
What this means is the browser cannot render anything until it has the CSS it thinks it needs, all downloaded and parsed. Blocked rendering is bad User Experience and it also affects negatively the FCP and LCP metrics that Google folks deem important.
Loading CSS as early as possible is ever so important. Normally CSS is discovered once the HTML is (even partially) downloaded and parsed. But we can do one better, thanks to the "hints" we can send with the HTTP2 headers of the HTML response. This way we tell the browser to start loading CSS ASAP without even waiting for HTML parsing and discovery.
Scary? Nah. I was able to accomplish this with 2 lines of code (or 3 if you're an overachiever) on my old-school cheap Dreamhost shared hosting with Apache server running this WordPress install. Here's how:
1. Make sure your host supports HTTP2,
2. Edit your .htaccess
,
3. ...and add two lines similar to these:
H2PushResource /wp-content/themes/phpied2/style.css H2PushResource /wp-includes/css/dist/block-library/style.min.css?ver=5.4.1
Some details...
Make sure your host supports HTTP2
I use Firefox and Brave, most people use Chrome or Safari. Between the 4 there are 3 versions of developer tools and waterfalls. So let's speak the lingua franca and use WebPageTest.org
Run a test on your site and look at the waterfall view. You can spot the HTTP protocol version when you click on the HTML file.
I ran a test on this site and here's my view:
Edit .htaccess
.htaccess
is just a plain text file in the root of your site that can tweak some Apache configuration. Since I'm talking about Dreamhost, here's their htaccess guide.
The two lines
OK, so what do you put in the .htaccess
? Going back to the test I ran I can see how 2 CSS files are preventing rendering.
Note how the blue bar (HTML download) has a dark blue section. This is HTML parsing. And immediately after it, the 2 CSS downloads begin. What we want to do is move the CSS downloads to the left, so all rendering starts (and finishes!) sooner.
So all you do it take the URLs of these two files and add them to .htaccess
with H2PushResource
in front. For me that means the URL to my custom theme's CSS /wp-content/themes/phpied2/style.css
as well as some WordPress CSS stuff.
While I was there I also added a JavaScript file which is loaded later. Why now start early? So the end result is:
H2PushResource /wp-content/themes/phpied2/style.css H2PushResource /wp-includes/css/dist/block-library/style.min.css?ver=5.4.1 H2PushResource /wp-includes/js/wp-emoji-release.min.js?ver=5.4.1
Again, the JS is optional and not relevant to the render-blockage unblocking.
Results
Here's the waterfall before the change:
Here's the waterfall after the change:
As you can see, in the "after" case the CSS download no longer waits for the parsing of HTML. As a result everything is faster - DOMContentLoaded, start render, onload, FCP, LCP. All the vertical lines happen 100ms sooner. Not bad for a few minutes of effort.
What happen as a result of these 3 lines of .htaccess
configuration is that the HTTP headers of the HTML now contain 3 preload links. That's how the browser knows to go fetch CSS even before downloading and parsing HTML.
Word of caution
What I describe here is a one-off thing for demonstration. As you can probably guess by the "5.4.1", when you upgrade WordPress or change themes, you might need to change the URLs of the preloaded CSS in the .htaccess
. Wouldn't it be nice if there's a plugin that does this automatically so you don't need to bother with every update? LMK if there's one, I'd love to set and forget.
We can do better
We can. I cannot because the shared Dreamhost hosting doesn't let me edit Apache config beyond .htaccess
. But if you have access to the server or virtual host config, see here.
Basically all you need to do in addition to what we did in .htaccess
is to add:
H2EarlyHints on
And that's it. This will send a 103 Early Hints
HTTP response with the CSS files even before the 200 OK
response. Only in supported browsers, which is basically Chrome v103 for now (Get it? v103 supports 103 responses), this means that if WP is still working on the HTML response (e.g. fetching data) CSS can already start downloading. This is a huge improvement. For more, check this Twitter thread and this post.
A picture says...
Here's an attempt to represent these 3 cases "graphically":
// #1 status quo connect ---> 200 OK header (no CSS hints) ---> HTML ---> CSS // #2 with hints as per this blog connect ---> 200 OK header (with CSS hints) ---> HTML ---> CSS // #3 with hints in a 103 response connect ---> 103 (with CSS hints) ---> 200 OK ---> HTML ---> CSS
Obviously #1 is the worst, that's how it's always been.
#2 described above with CSS hints is better.
#3 with the 103 response may be about the same as #2 if CSS is small and HTML is fast (e.g. static). But if the CSS is big or HTML is slow #3 may be a dramatic win. While the server is still checking databases and working on the 200 OK response the browser has had a chance to download and parse CSS and have it ready.
Take care now
Bye-bye then. Make the Web faster!
Update: same but in PHP
Some folks wondered on Twitter how to do the same with NGINX. I think alternatively the same can be done in PHP instead of .htaccess
. This should work in all servers.
In the functions.php
of your WordPress theme add:
function hints() { header("link: </wp-content/themes/phpied2/style.css>; rel=preload, </wp-includes/css/dist/block-library/style.min.css?ver=5.4.1>; rel=preload"); } add_action('send_headers', 'hints');
This sends the same headers but using PHP. Now if someone experienced with WordPress plugins wants to turn this into a plugin, so we don't have to hardcode CSS paths, I'd love it!
Comments? Find me on BlueSky, Mastodon, LinkedIn, Threads, Twitter