Getting up to speed with Google Accelerated Mobile Pages
Google’s Accelerated Mobile Pages (AMP) project is an open source library of very fast HTML Web components that can help us build very fast websites, very quickly.
It’s been around since late 2015 and is very much a work in constant progress. Evolving and growing daily, with ambitious plans for the future and a team of over 500 contributors.
It’s also a set of rules, that if we follow, mean that Google will cache our pages on their very fast CDNs. And when a mobile user searches for one of our pages Google will even begin partially prefetching it, giving near-instant page loads.
We’ll cover the fundamentals of AMP, view a snapshot of some components that are currently available and see how we can build the rich, interactive websites we want while still following the rules.
Where to begin
We write AMP in HTML but in order to do so we must first include the scripts for the components we want to use.
At its most basic, we just need to include the ~77kb AMP runtime script in our document’s <head>
(line:5 below) and then we’re able to use a few basic, built-in AMP components, such as <amp-img>
(line:10–11 below).
The AMP runtime provides the platform for the other AMP components to run on and also manages resources - lazy loading everything. And this <amp-img>
component provides the required interface to load up images.
For all the other components we also need to include their respective libraries. For example, if we wanted to use <amp-carousel>
(line:15–23 below) we would need to include its script (line:6–7 below).
That alone gives us the ability to use the multitude of AMP components and to build very fast websites, however, if we also want to harness the power of the AMP cache and Google search prefetching then we need to follow the rules.
The rules
1 — Boilerplate
There’s some mandatory boilerplate. Below is what it actually looks like on the page, specifics can be found in the official Getting Started docs.
2 — HTML
While most HTML is allowed, some tags that could slow down or block loading of the page are prohibited or have amp replacements.
Replacement tags include:
======================
<img> => <amp-img>
<video> => <amp-video>
<audio> => <amp-audio>
<iframe> => <amp-iframe>Prohibited tags include:
========================
<frame>
<embed>
<style> => With exceptions
<script> => With exceptions
3 — CSS
Our custom CSS must be contained in one internal <style amp-custom>
tag (line:5–9 below) that does not exceed 50kb, does not use !important
and also follows some other CSS rules.
To allow for bulky CSS keyframe animations there is also a <style amp-keyframes>
tag that can be up to 500kb.
4 — JavaScript
The <script>
tag can only be used to store data (state). It can also load AMP libraries but it can’t be used for any custom JavaScript. So, unfortunately, JavaScript breaks the rules and is prohibited… for now.
If we can abide by these rules we have a valid AMP page which will be cached in the Google CDN and prefetched for Google mobile search users. Validity can be checked while developing, in CI/CD and in production.
A page that doesn’t follow all the rules but still uses some AMP features is called a dirty AMP page by some, it will still work fine and be very fast but won’t get the other benefits of AMP.
The restriction on custom JavaScript has a huge impact for web development and is enough to put some people off, but AMP solves too many problems and is gaining too much popularity to ignore, with over 4 billion AMP pages from over 25 million domains including the who’s who of top-tier websites being reported in early 2018.
These restrictions aren’t there just for a laugh or to annoy developers, or even more sinisterly to try and control the web as far as I know. All this is being done to put the User first, so that we build better web experiences, sacrificing everything else that gets in the way of that mantra.
It’s not time to throw everything else we know about web development out of the window just yet but it is worth taking on AMP as another very handy tool in our toolbox. A tool that does give us ways to do most of what we want. So, let’s get a taste of some of the components and implementation patterns.
Layout
AMP is a little obsessive about knowing where things go on the page and what their dimensions need to be without having to fetch resources. Layout makes two appearances:
- A multipurpose container component that knows how to size itself and what its pixel dimensions or aspect ratio will be. It’s a built-in component of the AMP runtime.
- And an AMP-HTML attribute that lets individual components know how to do the same.
They have a helpful collection of values and depending on the type of layout, width
and height
may or may not be required, and can act as pixel dimensions or aspect ratios. If no layout
is present, AMP will try to infer it based on the presence of width
and height
.
These not only ensure minimal time is spent rendering the page in the client for the user but also give us developers a nicer API to size elements or containers, giving gains over writing responsive CSS.
Take the examples below:
We use an <amp-layout>
component with layout="fill"
to create an element that fills all available space, it’s green just to show its bounds.
Next we have an <amp-layout>
component with a layout="fixed-height" height="200"
(line:1 below) to make an element with a fixed height of 200px. It will take all available width, and is yellow.
Within that component we have an example of the layout
attribute on the <amp-fit-text>
component (line:2 below) that just scales the size of text to the requested size, in our case we have set layout="fill"
so it takes all the room of its parent.
Finally, a responsive <amp-img>
with an aspect ratio of 1.6 by 1.
If we put that all together here’s what it might look like:
Templating
AMP has an <amp-mustache>
component that allows logic-less templating via mustache.js (mustache docs), the key features as laid out in the official docs are:
{{variable}}
: A variable tag. It outputs the the HTML-escaped value of a variable.{{#section}}{{/section}}
: A section tag. It can test the existence of a variable and iterate over it if it's an array.{{^section}}{{/section}}
: An inverted tag. It can test the non-existence of a variable.
<amp-mustache>
doesn’t work on its own, it must be used as the templating solution within a parent that can get data from a CORS JSON endpoint, such as <amp-list>
.
Take this example template that uses an <amp-list>
to get iterable data from the Star Wars API and <amp-mustache>
to display each iteration:
{{name}}
(line:4 below) writes out the value of the attribute from the returned response array.
{{#url}}
(line:7–9 below) the variable holds a value so it is treated as a conditional, the <a>
within is only rendered if url
exists.
{{#films}}
(line:12–18 below) returns an array so it is treated as a loop, within which we have chosen to nest another CORS JSON endpoint in an <amp-list>
so we can get the names of the films each character has appeared in. {{.}}
(line: 13 below) represents the value of that item in the array.
Media components
Aside from <amp-img>
which we’ve already seen, there are replacements for audio, video and others. While most components can have a fallback
and noscript
tag, resources like these can also automatically gracefully degrade when you supply a cascading set of <source>
s for it to filter through (line: 20–21 below).
There are also many proprietary components that make it easy and fast to load content from sources such as YouTube, Instagram, Twitter, Imgur, Hulu, Soundcloud, etc.
Here we use a layout component called <amp-carousel>
to cycle through and display several media components.
Actions and Events
The on
element attribute provides us a means to trigger actions on user events. Implementation follows the below pattern:
on="event:target[.action[(...args)]]"
1 — event
event
is the name of the event that the element listens for. The tap
event is available on all elements and is the most straightforward way to implement a click event listener. Elements individually expose other useful events relevant to them such as change, submit, input-debounced
.
2 — target
target
is the DOM #id of the element to trigger the action on. It can also call the AMP runtime itself via AMP
to do things like navigateTo(), setState()
.
3 — action
action()
is the optional method to trigger on that element. It’s optional because elements have default actions. All elements have some common actions such as toggleVisibility(), scrollTo()
and element specific ones such as submit(), open(), close(), toggle()
.
4 — args
args
are optional key=value pairs, e.g. 'name'=event.value
.
In the following example we create a section
which is hidden
by default (line:3 below), a button
with a tap
event that targets the section
by its id
and toggles its visibility (line:1 below). Within that section
is another tag with an AMP.navigateTo
action (line:6 below) that takes us to a URL.
And finally, an input
(line:9 below) that will scroll to #my-image
(line:13–15 below) in however many milliseconds we input after a built-in 300ms debounce on the input-debounced
event.
You can read more about AMP actions and events in the official docs.
Dynamic components
We’ve already touched on <amp-list>
and <amp-mustache>
which gives us ways to retrieve and display JSON data dynamically. We could also use <amp-form>
to submit form data or make XHRs.
There are also some very handy components such as <amp-date-picker>
that gives a really great API and UX for date picking and <amp-geo>
that lets us vary content based on geolocation.
We can also serve up unsupported interactive content, including JavaScript, in an <amp-iframe>
. These iframes can’t be in the top 75% of the initial viewport or 600px, whichever is smaller. And they do not have access to the parent window outside of sending a postMessage
.
amp-iframe
amp-bind
But really, the big one here is <amp-bind>
. Bind enables data-binding and expressions, it was released in to the wild in 2017 and represents a substantial jump forward for AMP. It allows State, Bindings and Expressions:
1 — State
The setting and getting of state via hard coded JSON or a CORS JSON endpoint. We can define multiple mutable state objects.
State can be updated via the AMP.setState()
action. E.g. AMP.setState({state: {'name':'Chewbacca'}})
. We can also use pushState()
instead to add a new entry in the browser’s history stack so when our user navigates back they will restore the previous value of state.
2 — Bindings
Create data bindings between component/element properties and state. There are some bindable attributes common to all elements, e.g. height, width, text, class
and a host more that are specific to individual components, e.g. src, srcset, alt, placeholder, value, href
.
In the below example the text
of the h1
will dynamically update from state.name
. We can reference state by its #id. Bindings [...]=
only update after user action.
When bindings start to get too verbose or we want to make them reusable we can employ <amp-bind-macro>
to create callable expressions.
3 — Expressions
Writing expressions that can reference our state, using a subset of JavaScript. These expressions can reference the document’s state but not the document
itself or window
.
Below, on the tap
event of this button, we create a new state object with an #id of remoteState
and use an expression to create an endpoint for a random Star Wars character.
If we put that all together it could look something like this:
Presentation components
We have components like <amp-timeago>
which outputs a nicely formatted time difference, and <amp-mathml>
which formats mathematical formulas but the star here is <amp-story>
.
<amp-story>
allows the easy creation of magazine or digital storytelling formats with rich media and animation capabilities built in. There are extra features built in too for common magazine layout requirements.
It’s optimised for mobile devices in portrait mode. In landscape it gives a warning to rotate back to portrait and in wider viewports such as tablets and desktops it doesn’t expand responsively or adaptively.
There is an optional bookend after the last page which can have social media links, other relevant links, and call-to-action buttons. They may be statically generated or come from a JSON endpoint where some scope for customisation exists.
What are people doing with AMP
Here are some more examples of live AMP pages that use the components and strategies laid out above, and many more.
Resources
We’ve just dipped our toes in the pool, if you like the temperature then here are some resources:
Official website — https://www.ampproject.org
Github repo — https://github.com/ampproject/amphtml/
Loads of examples for all components — https://ampbyexample.com
AMP playground — https://ampbyexample.com/playground/
Starter templates — https://www.ampstart.com/
Official codelabs — https://codelabs.developers.google.com/
Thanks!
Thank you very much for reading.
If you have any feedback or questions please get in touch here on Medium or on twitter @sanj9000.
If you’re in London, UK then come along to one of our AMP meetups, and if you’re looking for a great place to work then we’re hiring great people!