When a browser requests a page, the server is sending the HTML as a first response. Then the browser starts working and parses the HTML to find all kinds of embedded assets like JavaScript and CSS. The browsers needs to go back to the server again and ask for these files. Server Push allows the server avoid this round trip and push the files directly together with HTML so no valuable time is lost. Many cloud hosting providers like Cloudflare start supporting HTTP/2 Server Push already: Announcing Support for HTTP/2 Server Push
Sounds great right? There is only one small problem: caching doesn’t work properly since Server Push is not aware of previous visits and will not check cached resources. Server Push always tells the browser that it is should receive the files from the server, whatever happened before.
Service Workers to the rescue!
If you want to know what Service Works are, read the Google Web Fundamental guide: Service Workers: an Introduction
“A service worker is a script that your browser runs in the background, separate from a web page, opening the door to features that don’t need a web page or user interaction. Today, they already include features like push notifications and background sync. In the future service workers will support other things like periodic sync or geofencing.”
Using Service Workers you can easily cache static files too and it is easy to understand since they are written in JavaScript! We are going to intercept the request for an asset and check if it is cached already on the device or not. If it is, just serve it from the local source instead of downloading it from the server.
Create a basic HTML template, containing an image and a JavaScript file we want to test on and register a new Service Worker: serviceworker.html
[html]
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>HTTP/2 Server Push testing page</title>
<meta name="description" content="Let me rank for Server Push, Google please?! Or I don’t allow you caching this page :-)">
</head>
<body>
<h1>I always want to rank for Server Push queries</h1>
Some random text that is not important right now
<img src="./images/very-important-image.jpg" alt="Server Push Example 1" width="500" height="500" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20async%20src%3D%22.%2Fjs%2Fvery-advanced-analytics-tracking.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%3E%0A%09if%20(‘serviceWorker’%20in%20navigator)%20%7B%0A%09%09navigator.serviceWorker.register(‘.%2Fserviceworker-serverpush-example.js’).then(function(registration)%20%7B%20%2F%2F%20If%20you%20did%20the%20right%20thing%0A%09%09%09console.log(‘ServiceWorker%20registration%20successful%20with%20scope%3A%20’%2C%20registration.scope)%3B%0A%09%09%7D).catch(function(err)%20%7B%20%2F%2F%20If%20you%20failed%20installing%20the%20library%20or%20did%20something%20else%20wrong%0A%09%09console.log(‘ServiceWorker%20registration%20failed%3A%20’%2C%20err)%3B%0A%09%7D)%3B%0A%09%7D%0A%09%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" />
</body>
</html>
[/html]
Next step is to make sure you have installed SW Toolbox: https://github.com/GoogleChrome/sw-toolbox/ which is a small library to create your own service workers. Then use the following code to listen to anything happening on /push and the image and JS folders: serviceworker-serverpush-example.js
[js]
(global => {‘use strict’;
importScripts(‘sw-toolbox/sw-toolbox.js’);
// The obvious route for any image or js requests
toolbox.router.get(‘/push’, global.toolbox.fastest);
toolbox.router.get(‘/images/(.*)’, global.toolbox.fastest);
toolbox.router.get(‘/js/(.*)’, global.toolbox.fastest);
// lets listen in on all page events
global.addEventListener(‘install’, event => event.waitUntil(global.skipWaiting()));
global.addEventListener(‘activate’, event => event.waitUntil(global.clients.claim()));
})(self);
[/js]
This will do the following:
- On the first load it listen to everything that is pushed via HTTP/2 Server Push and caches on user device via the launched Service Worker
- Next visit the Service Worker listen in again and will intercept if it recognize any cached resources
- The result will be a smaller Waterfall charts in your loading speed tools 🙂