Taking advantage of HTTP/2 push can bring big improvements to front end performance. This pushes resources to the browser preemptively, avoiding extra requests and waiting time. I’m going to cover how to do this in WordPress.
This has made big improvements to page load times, removing connections and waiting times for resources, as well as doing transfers in parallel.
To do this, I used HTTP headers instructing the browser to
preload resources. This isn’t http/2 push, but it directs the browser to fetch things in advance while the initial request is still transferring. The browser can then request all of them at once in parallel. For example:
header("Link: </wp-content/themes/tomjn/style.css?ver=5>; rel=preload; as=style", false);
To start with, I did this manually in
functions.php, and it helped in particular with Google Analytics and Typekit.
Actively Pushing Files With Nginx
To get Nginx actively pushing data to the browser, I added the
http2_push_preload on; directive to my sites config, and reloaded Nginx. This pushes any resources with relative URLs in the http header taking the
Link: </...>; rel=preload format mentioned above. It only works for relative URLs on the same domain though, falling back to the previous behavior for Typekit and other external resources.
nghttp command can be used to confirm this is working:
❯ nghttp -ans https://tomjn.com ***** Statistics ***** Request timing: responseEnd: the time when last byte of response was received relative to connectEnd requestStart: the time just before first byte of request was sent relative to connectEnd. If '*' is shown, this was pushed by server. process: responseEnd - requestStart code: HTTP status code size: number of bytes received as response body without inflation. URI: request URI see http://www.w3.org/TR/resource-timing/#processing-model sorted by 'complete' id responseEnd requestStart process code size request path 13 +20.86ms +644us 20.22ms 200 3K / 2 +44.97ms * +19.24ms 25.73ms 200 33K /wp-includes/js/jquery/jquery.js?ver=1.12.4 4 +50.87ms * +19.30ms 31.57ms 200 3K /wp-includes/js/jquery/jquery-migrate.min.js?ver=1.4.1 6 +51.09ms * +19.32ms 31.77ms 200 3K /wp-content/uploads/2016/11/favicon.png 8 +53.43ms * +19.33ms 34.10ms 200 4K /wp-includes/js/wp-emoji-release.min.js?ver=5.0.3 10 +53.56ms * +19.36ms 34.20ms 200 753 /wp-includes/js/wp-embed.min.js?ver=5.0.3 12 +53.68ms * +19.36ms 34.32ms 200 639 /wp-content/plugins/jetpack/_inc/build/widgets/milestone/milestone.min.js?ver=20160520 14 +54.32ms * +19.38ms 34.94ms 200 4K /wp-includes/css/dist/block-library/style.min.css?ver=5.0.3 15 +63.11ms +20.91ms 42.20ms 200 12K /wp-content/plugins/jetpack/css/jetpack.css?ver=6.9 16 +63.88ms * +19.39ms 44.49ms 200 14K /wp-content/themes/tomjn/style.css?ver=5 18 +82.97ms * +19.41ms 63.56ms 200 27K /wp-includes/css/dashicons.min.css?ver=5.0.3
Files transferred with a
* were sent via HTTP/2 push.
Automating Pushes in WordPress
To avoid needing to keep my preload headers up to date, I explored auto-preloading enqueued assets. This plugin tries to automate sending headers for enqueued scripts:
It acts at the last moment of the
template_redirect action, but this means only a subset of scripts and styles are caught. Sadly, it’s not possible to do this with the print styles actions. In the future I might experiment with output buffering to delay the first byte long enough to run on this script.
Pushing inline scripts would also need full page output buffering. This would kill performance though in the same way that Autoptimize destroys time to first byte.