Sanjay Purswani

@sanjsanj

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.

Example user flow illustrating how AMP pages can load almost instantly

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.

AMP — “Fast by default”

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 bare bones HTML needed to load the AMP runtime and use a built-in component
Output of above HTML

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).

Example of auto-playing carousel
Output of above HTML

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.

Bare minimum valid AMP HTML boilerplate

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.

amp-custom CSS style tag

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.

Video of what’s allowed by the AMP rules

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.

“Do what’s best for the end user experience “— https://www.ampproject.org/about/amp-design-principles/

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:

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:

Output of above examples of layout

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.

Example of iterating over JSON and templating the response
Output of above amp-mustache and amp-list example

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).

Example media components, with fallback and alternative sources

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.

Example of various media components in a carousel
Output of above example of 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.

Examples of actions and events
Output of above example, showing scroll duration determined by input value

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

interactive embedded google maps iframe component
JavaScript enabled google maps 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.

Examples of valid amp-state

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.

After user action the default text will be updated with whatever is in state.name

When bindings start to get too verbose or we want to make them reusable we can employ <amp-bind-macro> to create callable expressions.

Example of amp-bind-macro creation and consumption

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.

Example of using expressions

If we put that all together it could look something like this:

An example using state, binding and expressions
Output for above HTML
Video introduction to amp-bind

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.

Example of magazine article built with amp-story

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.

Non-mobile experience for amp-story

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.

Example bookend for amp-story

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.

AliExpress
Evening Standard

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!

More by Sanjay Purswani

Topics of interest

More Related Stories