Tom Butler's programming blog

Eliminate Render Blocking CSS using HTTP/2 Push

What does pagespeed mean by "Render Blocking CSS"?

If you've used Google PageSpeed Insights you may have come across the suggestion to "Eliminate Render Blocking CSS". It's a common problem with a few rather poor suggested fixes floating around online.

Before you can eliminate something you have to know what it is. So what is "Render Blocking CSS"?

When someone connects to your website and requests index.html, the browser downloads the HTML file, parses the code file and sees the <link> tag pointing to stylesheet.css. Then, the browser goes off and downloads stylesheet.css.

Before the browser can draw (render) the page on the screen it has to have downloaded both the HTML file and any required CSS files.

The problem is, it receives the HTML file first, parses the HTML file but then has to wait for the CSS file to download before the page can be shown.

Eliminating the render blocking CSS

There are a lot of rather hacky solutions floating around the web. Applying CSS with javascript, <style> tags, giving elements style="" attributes (eww) and other ways of deferring the loading of the CSS.

Several approaches are fundamentally flawed because they result in a Flash of unstyled content. It's obvious when you think about how they work: They wait for the browser to download and show the HTML file and then download the CSS file. Once downloaded, the CSS is applied.

The correct solution is sending the CSS and the HTML at the same time. While this can be achieved using <style> tags, that's far from an optimal solution either. It requires sending all the CSS on every page, wasting bandwidth, and potential maintenance problems with repeated code.

The solution here lets you keep best practices (no style="" attributes or <style> tags) and still get a 100 on google pagespeed.

Enter HTTP/2 Server Push

HTTP/2 Server Push is a technique that allows you to send multiple files in the same request. You can send both the CSS file and the HTML code at the same time. When the browser receives the HTML, it already has the CSS file to apply to it, eliminating the need to go off and fetch the CSS in a separate request. It is no longer blocked by the second request.

This is done with the link header. When the user connects to index.html, the following header can be sent:

Link: </style.css>; as=stylerel=preload

You can add as many CSS files, images and javascript files as you like. However, in practical terms you should only send the bare minimum. You can work out what should be listed here by letting google pagespeed identify what it classes as "render blocking".

Example with multiple files:

Link: </style.css>; as=stylerel=preload, </font.css>; as=stylerel=preload

When this header is set the web server sends the contents of index.html and style.css and font.css in the same request. You can think of it a bit like adding an attachment to an email. The link header tells the server to attach style.css and font.css to the request to index.html and the browser receives them all at the same time.

Setting it up

You can easily set this up for NGINX and Apache. Before you add any configuration you must enable HTTP2 in your webserver.

For NGINX you can use the add_header directive in your location block:

location = / {
        
add_header Link "</style.css>; as=style; rel=preload, </font.css>; as=style; rel=preload";
    }

And with apache, you can use .htaccess:

Header add Link "</style.css>; as=style; rel=preload, </font.css>; as=style; rel=preload"

Now try running PageSpeed insights and you should have removed style.css and font.css from the render blocking error!

But we're not done yet...

Inefficient server push

The problem with this approach is that every time someone views any page it will push the CSS files, even if the browser already has them cached!

Instead, we want to send the CSS on the first request only. To do this, NGINX recommends using a cookie. On the first request, a cookie is set and the CSS files pushed, then on subsequent requests, if the cookie is set, nothing is pushed. This allows selectively pushing CSS to browsers the first time they connect.

To do that with nginx you can use the following:

server {
    
location = / {
        
add_header Set-Cookie "session=1";
        
add_header Link $resources;
    }
}
map $http_cookie $resources {
    
"~*session=1" "";
    default 
"</style.css>; as=style; rel=preload, </font.css>; as=style; rel=preload";
}

This will set the cookie and only push the CSS if the cookie is not set.

To accomplish the same with Apache's .htaccess you can do the following.

<If "%{HTTP_COOKIE} !~ /httppush=1/">
    
Header add Link "</style.css>; as=style; rel=preload, </font.css>; as=style; rel=preload"
    
Header add Set-Cookie "httppush=1"
</If>

Conclusion

That's it! This is the most efficient method of avoiding render blocking CSS and getting your pagespeed score up. This page uses this technique and achieves a pagespeed of 100 for both desktop and mobile.