Faster WordPress rendering with 3 lines of configuration

July 2nd, 2022. Tagged: HTTP, performance, tools

"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:

HTTP2 version in WebPageTest

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.

Blocking CSS in WebPageTest

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:
Waterfall before the change
Here's the waterfall after the change:
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.

Headers after the change

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!

Tell your friends about this post on Facebook and Twitter

Sorry, comments disabled and hidden due to excessive spam.

Meanwhile, hit me up on twitter @stoyanstefanov