Our experience converting xwp.co 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.
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 xwp.co 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
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.
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
- 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
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.
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
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.
topposition 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:
<amp-position-observer on=" exit:slideinNavAnimShow.start; enter:slideinNavAnimHide.start; " layout="nodisplay"> </amp-position-observer>
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
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
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]
Handling SVG Sizes
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.
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.
- Shim support for GF form submissions in AMP.
- Workaround GF form validation issue by using HTML5 form validity instead of relying on server-side validation for informing users of the problem.
[Third Party Service Incompatibility]
AMP Loading “Spinner”
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 xwp.co home page, we observed the following improvements.
|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.
|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.