Using HTTP2 Push With WordPress

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.

All these requests used to block each other, now they’re loaded in parallel without wait times

This has made big improvements to page load times, removing connections and waiting times for resources, as well as doing transfers in parallel.

Preloading Files

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.

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

https://gist.github.com/tomjn/7fe22a4ec20f2565004bd216e9d1f497

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.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.