August 18, 2016, by Ruadhán O'Donoghue
So you're interested in the super-fast load times that AMP web pages offer, and you're titillated by the rich, native-like experiences and functionality that Progressive Web Apps (PWAs) can deliver! How do you provide a seamless and instantaneous bridge between the two?
<amp-install-serviceworker> of course!
In this article we take a look at this AMP tag and build an example of how to bootstrap a PWA service worker via an AMP page.
AMP pages are super-fast, stripped-down and highly cacheable HTML pages. It's a technology being pushed by Google and its partners, and it's gaining a lot of traction. It's being embraced wholesale by site owners and publishers in droves. Why is it being embraced so much? One reason is because it gives site owners a verifiable way to build fast loading pages. AMP pages often load much, much faster than their non-optimised HTML counterparts.
AMP is about to get a lot more important. Google just announced AMP is coming to general search results, not just the carousel of AMP results it was previously confined to. Some users are already seeing AMP pages mixed in with their search results:
In fact, Google will be highlighting AMP results with a small ⚡ symbol in the search results. Even if your non-AMP page loads as quickly as an AMP page, it won't have the ⚡ symbol, which, right or wrong, means you'll be at a disadvantage. The AMP lightning bolt to the side of a search result is a big draw: you know this page is going to load quickly. If you're not familiar with AMP, you can learn about it here.
Why Progressive Web Apps?
Web technology has evolved to a point where it can offer rich user experiences, with features such as offline capabilities and push messaging that were previously only available to native apps. So, people are justifiably excited now that the web can offer native like functionality without the drawbacks of native apps (such as app store gatekeepers, discoverability, linkability, and maintainability). PWAs represent a coming of age of the web where there are few limits to what can be built with web technologies.
The service worker, which can be thought of as a client-side, programmable network and caching proxy, is one of the core technologies behind PWAs. You can read more about PWAs here, and service workers here.
Introduction to amp-install-serviceworker
At Google I/O 2016,
<amp-install-serviceworker> got a lot of attention when the Washington Post mentioned that they were using it to bridge the gap between lightning fast load times of AMP pages, and the first load of a cache-enabled PWA. The problem it solves: delivering a seamless, near instantaneous hand-off from an AMP page to a PWA page.
It's the installation of a PWA service worker via an AMP page that enables this bootstrap, and it's the
amp-install-serviceworker tag that makes this possible. So, let's see how it works.
First, before we can use
<amp-install-serviceworker>, we need to import the
amp-install-serviceworker component from the AMP project CDN. Just add this line to the head of your AMP document:
If you're curious, you can view the unminified source of this component on github. It's interesting to note this function:
The helpful comment explains the point of introducing a delay before installing the service worker: so that accidental or brief visitors don't get the service worker installed unnecessarily.
Now to the
<amp-install-serviceworker> tag itself:
The two interesting attributes here are
src attribute points to the service worker file that is to be installed. Because AMP files can be served from the AMP cache, as well as from the origin server, some mechanism to install the service worker from a separate origin is also needed. This is the what
data-iframe-src is for.
When the AMP document is served from the same origin as the service worker, the
src attribute is used. When the AMP document is served from the AMP cache, the
data-iframe-src document is used to install the service worker. Note that
data-iframe-src points to a page that installs the service worker-we'll come to this later-and the
The AMP page
Now that we know a little bit about
amp-install-serviceworker, let's take a look at the rest of the AMP page.
First, AMP pages need to start with
<!doctype html>, followed by
<html ⚡> or
<html amp> (ironically, that lightning symbol is slow enough to type on today's keyboards; copy-paste is your friend here!).
Next include some standard AMP boilerplate:
It's good practice to add a
canonical link to a non-AMP counterpart page, to associate the two pages. Search engines will show the appropriate link depending on the user's device.
The full AMP page, with our
amp-install-serviceworker tag looks like this:
To summarise the AMP page, note that we
- use a canonical link to point the AMP page to the PWA to associate the two
sw.js) file in the
- point to a service worker HTML installation page in the
- have added a simple logo (using
amp-img), some navigation, and some simple styling
So that's our AMP page, now let's look at the PWA and the service worker.
The service worker
Now that we have a method for pointing the AMP page at a service worker, let's take a look at the other side of the setup: the service worker and the PWA page.
The point of all this is so that we get a lightning fast page load when a user visits the AMP page, and then when the user follows a link to the PWA site, this is also very quick because the service worker was installed and pre-cached some of the PWA resources in the background.
So the service worker needs to download some of the resources needed to get the PWA set up when it's installed. To demonstrate the concept we'll create a simple service worker to download and cache the PWA page and its resources. If all has gone according to plan, then we should see that the resource was delivered via cache, despite the user never having visited this page before.
Service worker example code
We'll make our job a little bit easier by using the
sw-toolbox service worker library. This library will help us pre-cache resources, and implement an appropriate caching policy without having to code all the details.
So, first we include
sw-toolbox. You can grab it directly from github with
git clone https://github.com/GoogleChrome/sw-toolbox.git or via
npm install --save sw-toolbox, and deploy it along with your application. We used
npm to install the toolbox in our
lib folder, so to include it we use:
Next we tell the service worker to cache some resources. We use the
toolbox.precache function to do this. It expects an array of URLs of resources to cache, and it must be called before the install event is triggered. For this example, we'll just cache the main PWA page, and the logo. For a proper progressive web app you'd probably precache more resources than we are here; for now we'll keep it simple. Most PWAs would have a
This will cache all the things we need to deliver the PWA page. However, although these items have been added to the cache, we still need to set up the service worker to serve them from the cache, to ensure a quick load. The sw-toolbox supports express-style routing, so you can target specific URL and URL fragments to be served with a particular caching policy e.g.
networkFirst. However to keep it simple for this example, we'll just set the service worker to serve using
cacheFirst policy for everything.
The full service worker code is:
(Strictly we don't need the last three lines, but they're useful for console debugging!)
The Progressive Web App page
Strictly, for our page to qualify as a PWA, there are a few requirements, such as having a manifest file, having a service worker, and so on ( more on these requirements here). We won't be too worried about ticking all these boxes now; the important thing is to get the service worker bootstrapped.
The service worker iframe installer
We also have to provide the
sw.html file we referenced in the
data-iframe-src attribute. This page can be pretty simple. It just needs to load a service worker in the usual way. Strictly, we could simply load the PWA page itself in the iframe, and not bother with a dedicated service worker installer page, but it makes sense to just include the minimum code needed for installing the service worker at this point, and so we add this page
So that's basically it. Now, to test that it has all worked, you should do the following:
1. First visit the AMP page: https://mobiforge.github.io/amp.html
If you add #development=1 to the link (i.e. https://mobiforge.github.io/amp.html#development=1), you can keep an eye on what's happening via the console log.
All going to plan, it should silently install the service worker in the background, and the service worker will download some resources. You can check the console to see that the service worker has installed and added the items to the cache.
2. Next visit the PWA page: https://mobiforge.github.io/pwa.html
Remember we set the service worker to only serve via cache-first policy. If you want to be really convinced, you could switch your device to offline/airplane mode before visiting this page.
There you have it, the best of both worlds!
Bonus: You don't need to let Google control who gets to see the AMP page
You don't need to wait for Google to crawl your site and notice that you have an AMP page, so that it can then decide whether to surface an AMP link to your visitors. There's nothing to stop you using device detection to target mobile devices to deliver the AMP page on first visit: if they haven't visited before, and are on a mobile device, then serve the AMP page. On subsequent visits and page loads then serve the pre-cached, instant-loading PWA site!