Improve WPO with HTTP/2 Server Push

Ramón Saquete

Written by Ramón Saquete

In this post we are going to explore an advanced WPO technique, which when used correctly, is going to significantly improve our WPO metrics, especially for users who enter our website for the first time, without affecting the experience of recurring visitors, or perhaps, even improving it as well. It is one of the relatively new HTTP/2 protocol improvements, which simply consists in an ability to respond to a client’s request with additional files to the ones it has requested, pushing or forcing their download from the server, which is why this technique has been named “HTTP/2 Server Push“.

How should we apply HTTP/2 Server Push?

Usually, when we request a page from the browser, the server returns the HTML and the browser analyses it with an algorithm called preload scanner, which it uses to look for the resources it has to request to the server before starting to build the page. HTTP/2 Server Push allows us to download resources in advance, so that they’re downloaded together with the HTML, preventing the browser from having to request them after downloading and analysing the HTML.

To be able to use this feature, both the client and the server need to be ready to use it. In the present day, all browsers enable it, but not all web services with HTTP/2 enable use of HTTP/2 Server Push. For it to work, we usually will need to have the most updated versions of the web service and their respective modules or extensions for HTTP/2. There aren’t many hosting or CDN services enabling their use for now.

To apply this technique correctly, we must deliver critical resources for new visitors immediately: first, let’s remember that critical resources are those which are necessary to display the part of the page that the user views first. For example, on a website that generates the HTML client-side with JavaScript, it will be a critical resource, but in a different case it may not be one. In general, some parts of the CSS, some fonts and exceptionally, some images are always going to be critical resources, basically, everything that is used to paint the important parts of the above the fold, to make it interactive. However, it is not advisable to download all critical resources in advance, because we could slow down the HTML download, which is the most important critical resource of all. For the most part, I do not recommend delivering images immediately, and to always test with a WPO tool like Lighthouse or webpagetest.org whether the way in which this technique is applied is optimising or deoptimising the page load, painting and interactivity.

HTTP/2 Server Push improves WPO 🚀 for new visitors, only if it's applied to really critical resources.Click To Tweet

The application of this technique on critical CSS has the advantage that it is no longer necessary to inline it in the HTML, which means it can be cached by the browser, and that it doesn’t increase the size of the HTML for recurring visitors, and with that they also get a small improvement. Similarly, if we download an image in advance, we won’t have to inline it in the HTML and it will be cached.

HTTP/2 Server Push allows us to apply the technique of delivering the critical CSS immediately, without increasing the size of the download for recurring visitors 👍Click To Tweet

HTTP/2 Server Push implementation

With this implementation, besides pushing some resources, we also have the option of preloading them. We’ll examine the difference below, and see how it’s implemented in each case.

Without preload and without Push

First, let’s see a case without optimisation. Imagine we have an HTML page, which links CSS and an image, and inside the CSS we link a font. The download over HTTP/2 would usually happen in the following manner:

http/2 without push

The image and the style sheet are downloaded simultaneously, because both are referenced from the HTML, and HTTP/2 allows to send several multiplexed files at the same time.

Push

Now we want to optimise the previous case, and we suppose that the CSS in full and the font are critical resources. We want to download these resources immediately, so that it works as follows:

HTTP/2 with push

To implement it, we simply need to include the resources we want to “push” inside the header of the requested file, using the Link parameter, in the following way:

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

We can have these values in the he “as” attribute or we can omit it if it’s an HTML document.

Preload

If we include the nopush keyword, instead of sending the resource simultaneously, it will be requested when the browser analyses the request header during the HTML download. This will be called preload, as it’s not a push.

Link: </app/style.css>; rel=preload; as=style; nopush

This behaviour is similar to the one we would have if we use the same directive in the HTML of the page in the following way:

<link rel="preload" as="style" href="/app/style.css" />

By doing the latter, the browser downloads the HTML and upon encountering this tag in the header it requests the resource to the server. This won’t make much sense if we request a style sheet that it’s going to encounter either way in the HTML header. It will make sense, however, to download in advance a font linked from the CSS or any other resource that it’s going to find inside the third level of the dependency tree of the resources and we don’t have HTTP/2 Server Push, because we prevent the browser from downloading and analysing the CSS to request it. Here’s a visual representation of it:

HTTP/2 without Push with font preload

Resources pushed over the network waterfall

When we implement a Push, we must verify in the network waterfall of the browser whether the resources we want are being pushed. If we’ve written the header incorrectly, they won’t appear pushed, and the download initiator will be “Parser“, which is the analyser of the preload scanner. And, if the server hasn’t enabled use of HTTP/2 Server Push, the download initiator will appear as “Other“, because the request will have been sent to the HTTP header, but it won’t be sent simultaneously with the HTML, behaving almost like a preload made from the HTML header instead.

Let’s see some examples of each case in Google Chrome’s network waterfall, which is the only one displaying the Push correctly:

jpg without preload
Normal page load where we have an HTML “document”, linking a CSS, which in turn links an image.

In this first screenshot we have an image, the initiator of which is “Other”, and it’s not downloaded until it starts to analyse the CSS. Let’s see what happens if we put rel=”preload” in the HTML header:

jpg with preload
JPG preload with Link header with rel=”preload” in the HTML.

The initiator is now the HTML analyser (parser), because now it is loaded once it starts analysing.

Now let’s see what happens with a Push:

Push in Google Chrome
This is how a Push should appear within the Network tab of Chrome Dev Tools.

In this case, a CSS has been pushed, so when we’ve finished downloading the HTML, the former has already been downloaded.

Be careful with the cache

We must keep in mind at the time of implementing Push that this optimisation should only be applied to new users, because recurring users will have this data in the cache, meaning we don’t need to send this data to them again. If the HTTP cache headers have been set correctly, the data will be sent with the “pushed” files.

If we push a cached file, its shipping will be cancelled as soon as the browser finds out that it already has the same resource cached, but we still will have wasted resources.

Given that in the request we cannot initially know whether the user is a new or a recurring one, the best strategy to follow here is to set a cookie for first-time visitors, and indicate within its value the version of the resources that have been sent.

This way, when we get a new request from the same client, we will know whether we should or should not send these resources with Push. Although not having the cookie does not guarantee that the resources are cached client-side, we will prevent the majority of unnecessary shipments.

In the future we expect more specifications to solve this issue, but for the time being it is up to the developer to solve it.

When we use HTTP/2 Server Push, we won't know which files are cached in the client if we don't control it with cookies 🍪Click To Tweet

Web service-specific implementation

There are more ways to implement Push besides adding headers to the HTML. We can also use directives specific to the web service in use, so that we can push resources even before the HTML has to be processed in the server. For example, in Apache we would do it using the H2PushResource directive from .htaccesss, which we could also use to indicate the priority of some Push over others, otherwise it will set the priority on its own based on file type. In the following example we give priority to the download of CSS over JavaScript using this directive:

 H2Push on
<Location /index.html>
    H2PushResource "/css/critical-styles.css" critical
    H2PushResource "/js/critical-javascript.js"
</Location>

Alternative ways to suggest how to load resources and other considerations

Besides being allowed to use the rel=”preload” attribute, either in the HTML’s link tag or inside the HTML’s Link header, we can also use the rel=prefetch, rel=”prerender”, rel=”preconnect”, rel=”dns-prefetch” and rel=”preconnect” values, to suggest the browser how it should load the resources. Here is the meaning of each of these values:

  • prefetch: is used to download resources that are going to be viewed after the current page, so they’ll be downloaded after the rest of the resources have finished downloading. Safari does not allow this.
  • prerender: same as the previous one, it is used to download resources that are going to be viewed after the current page, once the rest of the resources have finished downloading. In addition to that, the browser can start painting these resources when it has finished painting the current page and it has CPU and memory resources to spare. For the time being, not many browsers support this, so we recommend using the previous one.
  • dns-prefetch: it is used to perform in advance the DNS resolution of external domains, which load resources on our page. All browsers allow its use.
  • preconnect: is used to perform in advance the DNS resolution, as well as to establish the TCP connection and TLS negotiation (if HTTPS is used). With this value we need less roundtrips than with dns-prefetch, but the browsers supporting this are few.

Additionally, we can add media queries to load specific resources based on screen size. You can read on how to optimise fonts you use on a website here.

We can also apply the link tag values used for SEO purposes, such as rel=”canonical” or “rel=”alternate”, although they have nothing to do with suggesting in what way resources should be downloaded:

  • canonical: it is used to indicate the canonical URL to the crawler, to prevent the indexing of variants of the same URL as duplicate content. Setting this parameter in the header instead of the HTML’s body is the only way to set the canonical URL for files that are not HTML, and presently, it is only used for PDF files. For example:
Link: <http://www.-----.com/articulo.pdf>; rel="canonical"
  • alternate: is used to indicate an alternative version of the current URL to the crawlers, it can belong to a different language or language/country version, or to a mobile version. For example:
Link: <http://www.-----.com/articulo>; rel="alternate"; hreflang="es-ES"
Link: <http://www.-----.com/article>; rel="alternate"; hreflang="en-GB"
  • amphtml: is used to indicate the alternative AMP version for a page that is not AMP. For example:
Link: <http://www.-----.com/articulo>; rel="amphtml";

And finally, we can chain several Link headers, separated by commas in the HTTP header, just so:

Link: <//pagead2.googlesyndication.com>; rel=dns-prefetch,
</js/bootstrap.min.js>; as=script; rel=preload,
</ads.html>; rel=prerender,
</css/bootstrap.min.css>; as=style; rel=preload

This saves us some code with regard to entering a link tag in the HTML or a new Link parameter in the header for each resource.

Conclusions

HTTP/2 Server Push is not easy to implement, and when poorly applied, by downloading too many resources in advance or non-critical resources, and without taking the cache into account, we could worsen the performance for both new users and recurring ones. However, if we apply it correctly, it will considerably improve WPO metrics, especially for new users, allowing us to cause a better first impression with our website on said users.

Given that this technology is still partially in an experimental phase, its implementation will foreseeably change in the future, becoming easier to implement, because right now the fact that to Push something in the HTTP header we have to use code equivalent to a preload in the HTML header, and to make a preload in the HTTP header we have to add nopush, none of which really makes much sense.

It should also include some improvements, it would be interesting to be able to use it to speed up redirections, making the web service send a push from the HTML to where it’s going to be redirected automatically, which for the time being is not working, not even manually.

Bibliography

Ramón Saquete
Autor: Ramón Saquete
Web developer at Human Level Communications online marketing agency. He's an expert in WPO, PHP development and MySQL databases.

Leave a comment

Your email address will not be published. Required fields are marked *