AMP Up – Converting to Native AMP

Authored by

Our experience converting to Native AMP with the AMP WordPress plugin.

We’ve been working with Google and Automattic on the AMP for WordPress plugin for a little over 6 months. We were brought onboard to improve the functionality and user experience of the plugin and bring it inline with the many recent enhancements of the parent project. You can read more about this here, here, and here.

The vision behind the AMP Plugin project was to make it as easy as possible for anyone to have their WordPress site run on and leverage the performance upsides of AMP, particularly by adding support for Native AMP, where there is only one version of a page, with your canonical URLs serving AMP documents.

With some very cool architecting and engineering from the teams here and at Google, the plugin now does this. WordPress users can install the plugin and be 90% (sometimes 100%) of the way to having a fully Native AMP site that is completely inline visually and functionally with the previous non-AMP version. It’s not quite plug-n-play, but the term indeed comes to mind.


AMP WordPress Plugin Workload

Prior to WordCamp Europe in June, the v1.0-beta1 release of the plugin was at a stable enough state that we decided to bring our own site onboard (and do some dogfooding at the same time). The theme for was custom and used a traditional approach to WordPress page templates. It included a couple of Custom Post Types and worked with the popular custom fields plugin Advanced Custom Fields. It was built without AMP compliance in mind so it represented an excellent test-case for the plugin and the general experience of getting up and running on Native AMP.

We installed and activated the plugin over on staging and took the site through QA. This is what we found.

Things the plugin handled automatically

When serving an AMP response, the plugin updates the embed handlers to use AMP components where recognized. So a YouTube embed is output as an amp-youtube element instead of an iframe with a custom script. The plugin then uses output buffering to obtain the HTML document as rendered by a theme template. When the template has finished rendering at shutdown it takes this HTML and loads it into a DOM document. The plugin invokes a number of post-processors (“sanitizers”) which convert regular HTML into AMP where required, if not already done so (e.g. via embed handlers or a theme doing checks for is_amp_endpoint()). For example there is an image post-processor that converts img elements into amp-img elements, along with the required width/height dimensions if not present in the original HTML.

For other non-recognized embeds that output iframe elements, these will automatically get converted into amp-iframe elements.

The penultimate post-processor that runs is the style sanitizer. Not only does it collect external stylesheets and inline styles to combine into a single AMP-required style element, but it also converts the CSS selectors according to the conversions done by the other sanitizers (e.g. img.avatar becomes amp-img.avatar). The goal is to prevent you from having to rewrite your CSS for AMP. The most important feature the plugin provides to this end is automatic CSS tree shaking. Themes and plugins almost always enqueue more CSS than the AMP’s max-allowed 50KB. So the AMP plugin takes the parsed CSS and compares it with a given DOM document to then delete CSS selectors and then rules which are not relevant to the current page. This helps achieve one of the biggest challenges of converting to AMP.

So the AMP plugin takes the parsed CSS and compares it with a given DOM document to then delete CSS selectors and then rules which are not relevant to the current page. This helps achieve one of the biggest challenges of converting to AMP.

The final post-processor is the “whitelist sanitizer” that removes all elements and attributes which are not allowed in AMP. In previous versions of the plugin this sanitizer would remove such invalid elements and attributes silently. However, in the current version of the plugin the invalid elements and attributes are reported as validation errors. A site admin can choose to automatically accept a validation error for sanitization. Otherwise, such validation errors will be persistently presented to the user as warning notices when in native mode, and in paired mode such un-accepted validation errors cause the AMP version to redirect to the non-AMP version.

Things we didn’t strictly have to fix

The AMP plugin forces AMP responses to be valid AMP. If you have a site in Native AMP mode, it removes everything that is not valid AMP while also making sure that AMP’s required markup is in place (e.g. AMP boilerplate). When a site is in paired mode, validation errors are not sanitized by default and any such non-sanitized AMP validation errors the plugin encounters will by default result in redirection to the non-AMP version. It does this until you explicitly mark those validation errors as being acceptable for sanitization, or you select the checkbox to automatically sanitize all validation errors (as is the behavior in Native mode, since there is nowhere to redirect to). Even in the case where sanitization is automatic, the plugin still warns you when it encounters validation errors you haven’t marked as being accepted for sanitization. So whenever possible, it’s a good idea to fix those validation errors in your theme to reduce the noise. The things we did for our theme:

  • Use AMP’s required meta viewport (just adding minimum-scale=1).
  • Skip enqueuing scripts in theme when serving AMP, since they get stripped out anyway. These required adding AMP-specific alternatives in some cases (see below).

Things we had to fix (The final 10%)

The blue-sky nature of WordPress means that no plugin can fully account for all edge-cases. Unsurprisingly, there will always be some things that need manual attention. These things may include:

  • Compatibility issues with third-party services
  • Compatibility issues with your plugins
  • Compatibility issues with your theme
  • Bugs with the plugin itself

The powerful thing about this plugin is that when things fail, they fail gracefully. The majority of these will be due to JavaScript dependencies and will give the user an experience akin to having JavaScript turned off in the browser. (In fact, noscript elements will automatically be unwrapped in AMP, so no-JS fallbacks become AMP fallbacks by default.)

There are always gotchas, and since we were running a beta version of the plugin, we spotted a couple of (now fixed) bugs. These are the things we found that needed some extra attention.

Well-formed HTML

[Theme Fix]

We discovered some div closing tags were missing in our theme 🙄. While the browser’s DOM parser was able to recover from these in a desirable way, the DOM parser in PHP did not. So we had to fix up the well-formedness of our HTML to make sure that the PHP’s DOM document could be serialized in the same way that the browser does.

The Sticky Nav

[Theme Enhancement]


On the desktop viewport we have a navigation menu that appears and sticks to the top of the viewport when scrolling down past the nav menu that appears in the header. The theme was achieving this effect through the use of jQuery listening to the scroll event and then toggling a class on the sticky nav container to animate its top and opacity to show/hide. This approach does not work in AMP for a few reasons:

  • Custom scripts are not allowed (so that rules out jQuery).
  • CSS class names cannot be toggled by scrolling.
  • The top position cannot be animated (since it is not hardware-accelerated).

The solution in AMP involved the use of amp-position-observer and amp-animation. Instead of animating the top property, we instead switched to animating the transform property with translateY(): this has the same effect, but it has the bonus of being hardware-accelerated. So we created two amp-animation elements, one slideinNavAnimShow, and the other slideinNavAnimHide. Then we simply added the following amp-position-observer element inside the site-navigation element that appears in the header:


So when the user scrolls out of view of the header site-navigation element, the exit event happens and the slideinNavAnimShow animation starts. Then when they scroll back into view of the header nav, then the enter event happens and the slideinNavAnimHide animation runs. You can view the source code of the page to see the amp-animation definitions.

It turns out that a very similar approach can be used to add support for the sticky navigation menu in the Twenty Seventeen theme. To see an example of that, see the PR which adds AMP compatibility to the core themes.

The Mobile Nav

[Theme Enhancement]

In addition to the sticky nav menu, there was also a change we needed to make for the nav menu appearing in mobile. When clicking on the menu button (the hamburger) the menu needs to expand. Instead of listening for the click and toggling the class name with jQuery, we instead used AMP events and amp-bind. The approach here is the same taken to add support for the mobile nav menu in the core themes as well as demonstrated in an _s PR. Please see the full write-up on implementing a mobile nav menu on the plugin wiki.

Switch to AMP version of Google Tag Manager

[Third Party Service Incompatibility]

Like many sites, we use Google Tag Manager (GTM) to manage scripts we want to add to website pages. GTM has an AMP-compatible version which we switched over to. It’s important to note that it has a much smaller list of ready-made tags and does not accept custom tags outside of image-based ones. This is in line with the restriction on JavaScript AMP has, and it meant we lost the ability to keep using the Hubspot (CRM) tracking code. We’re working with Hubspot (see further down) to add an AMP-compatible option to their tracking code.

Handling SVG Sizes

[Plugin Fix]

We noticed that SVG images in the theme were not getting sized as expected. When the images had inline width/height dimensions specified, then there was no problem. The problematic images were those that lacked the specified dimensions. So we fixed the plugin to add support for obtaining dimensions from SVG images in a similar way the plugin was already obtaining dimensions for JPEG, GIF, and PNG images.

Gravity Forms

Plugins are often the culprit for issues with AMP. We use Gravity Forms on the site for various forms and found we had a few things to clean up around its compatibility with AMP. The following are patches that could be fleshed out further for wider compatibility fixes for the plugin.

[Theme Fix]

CRM Tracking

[Third Party Service Incompatibility]

We use Hubspot as our Sales and Marketing CRM which requires the inclusion of a small snippet of JavaScript on that page. JavaScript isn’t allowed in AMP. Of all the found issues, this is the one we are still working through as Hubspot doesn’t have any alternate tracking options. We are working with them to resolve this and develop an AMP compatible solution.

AMP Loading “Spinner”

[Plugin Extension]

The AMP plugin displayed a little loading spinner while images were loading on the page. Although it was only briefly, the spinner wasn’t visually in line with our website design. With this in mind, we introduced a custom sanitizer in our theme that turns off the spinner for all images. Something like this should perhaps be configurable in the plugin itself eventually.

The difference it makes on Performance

AMP is all about improving performance, for individual websites, and collectively the whole web. Hyperbole aside, this is the difference it made to our website.

TL;DR we observed an average 50% improvement on page load time

For the home page, we observed the following improvements.

Before After
Load Time 3.387s 2.395s
First Byte 0.391s 0.340s
Start Render 1.267s 1.567s
Speed Index 1597 1830
Time (Doc Complete) 3.387s 2.395s
Requests (Doc Complete) 64 36
Bytes In (Doc Complete) 779 743 KB
Time (Fully Loaded) 4.318s 3.812s
Requests (Fully Loaded) 71 42
Bytes In (Fully Loaded) 873 760 KB

For a project page, one of our Custom Post Types, we observed these improvements.

Before After
Load Time 6.463s 1.844s
First Byte 0.344s 0.346s
Start Render 1.200s 1.667s
User Time 1.844s
Speed Index 2639 1721
Time (Doc Complete) 6.463s 1.844s
Requests (Doc Complete) 64 24
Bytes In (Doc Complete) 1,450 KB 481 KB
Time (Fully Loaded) 7.619s 3.272s
Requests (Fully Loaded) 74 30
Bytes In (Fully Loaded) 1,466 KB 498 KB

Native AMP brings a host of performance enhancements to the table for WordPress sites, both big and small. Work on the AMP WordPress plugin, as well as the parent AMP project, is ongoing and, being open source, there will be many ways you can contribute. Please try out v1.0-beta1 for yourselves.

If you’re responsible for a larger web property and are interested in the implementation of AMP, get in contact to discuss what we’ve observed as the best way to approach projects like this.

[Thanks to Weston Ruter for his support on both this article and the actual implementation of AMP on]

One thought on “AMP Up – Converting to Native AMP”

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.