I have presented this subject a couple of times already at the Copenhagen WordPress Meetup and WordCamp Nordic, and thought that it would be valuable to make a blog post out of the WordPress & Accelerated Mobile Pages by Google combo, instead of sharing a plain context-less Keynote presentation.

This blog post is going to be presenting both our findings in terms of impact, for those who needs convincing, and how we implemented it on Falcon.io’s blog with WordPress and the AMP plugin, and patching in between.

I’ll assume that you are looking into AMP, so no need for further introduction. Let’s dig in.

Why AMP in the first place?

If you need convincing, or if your manager needs convincing about getting onboard with AMP, the main question is really: Why should we invest on mobile site speed?

The best answer I found are those simple numbers from Alberto Medina’s talk during WordCamp Europe 2018, summarizing the impact of speed on mobile:

53% of mobile site visitors leave after 3 seconds of load time
-7% lost conversions per 1 second delay
2X ad revenue for sites loading in 5 sec. vs 19 sec.

We started looking into AMP mid-2017, but it was not until February 2018 that we launched a first version of an implementation on our blog. At that time, the framework was still evolving very fast, and new releases with new features were appearing all over the place. The first months after having implementing AMP, there were a lot of ad-hoc work needed, when for example a new release would break something that would invalid our template, therefore un-indexing all of our 500+ blog posts at once. Good times.

After those initial hiccups, flash-forward to December 2018 to see the impact.

Site speed improvements

The first notable improvement was on the hard number of our mobile site load speed. We measured an average non-AMP post load speed, to a an AMP powered post, with Google’s tool Test My Site, and found the following results:

From 11s to 5s page load on 3G, we technically could keep 11% more visitors. That is hundreds or thousands of people we can keep engaging with, thank you very much.

Show me those 2000%

I thought it’d be better to show these numbers early on in the post, so that the actual implementation looks worth it. These are our pageviews taken from Google Analytics, for our organic traffic on mobile, for the period of January til December 2018.

A few points to nuance this obvious success project:

  • Of course, the organic traffic increase is not solely due to AMP, it needed to be paired with a consistent work on SEO, and needless to say a great content marketing strategy.
  • Even though the curve has been exponential, there is almost no chance that the increase will keep this rate in the future. Other companies have started to adopt the same tactic, and the growth will naturally stagnate as the framework will be more used.

All that said, implementing AMP has been a significant win in our books.

Where do I start?

First of all, you will need to install the relevant official plugins to have AMP on your WordPress installation. Obviously you will need the official AMP plugin, developed and maintained by Automattic and Google (can’t get more official than that).

Once the plugin activated, you will need to configure the settings. We choose to have the Classic template mode, and have AMP only supported for posts.

The second plugin you will need is Glue for Yoast SEO & AMP. As the explicit name of this plugin entails, it will allow you to make Yoast SEO work for AMP templates, and ensure that the meta-data is implemented. Obviously, this is needed only if you use Yoast SEO in the first place (which you probably do if you’re digging into AMP).

You will then get a new AMP submenu under your SEO admin menu, where you can make sure that it is enabled for posts.

Custom plugin

At this point, your posts are now accessible and indexable as Accelerated Mobile Pages. If you test one of your post and append /amp, you will get your content with the default AMP layout:

It’s working as it should. Congratulations.

But wait, this is not my brand, those are not my colours, this is not my layout.

You will need to create your own templates for that. If you are familiar with how WooCommerce build their templates for example, and how to extend them, then the steps are fairly similar. The AMP backbone template has plenty of hooks that you can use to inject your own template partials or scripts. This is the approach we’ll use to create our own AMP template.

First of all, let’s create a simple custom plugin that will handle our customizations (I am using an autoloader):

<?php
/*
  Plugin Name: Falcon.io - AMP
  Description: Custom template for AMP
  Author: Kristoffe Biglete
  Plugin URI: https://www.falcon.io
 */

namespace Falconio\AMP;

Class Main {
	const TEXT_DOMAIN = 'falconio_amp';

	public function __construct() {
		add_action( 'after_setup_theme', [ $this, 'bootstrap' ] );
	}

	public static function plugin_url() {
		return untrailingslashit( plugins_url( '/', __FILE__ ) );
	}

	public static function plugin_path() {
		return dirname( __FILE__ );
	}

	public static function get_template_part( $template_name, $vars = [] ) {
		extract( $vars );
		include plugin_dir_path( __FILE__ ) . 'Templates/' . $template_name;
	}

	public function bootstrap() {
		new Frontend();
	}
}

new Main();

As you can see, aside from the constructor and template methods, we’ll only need that Frontend class that will create our template.

Frontend

We will first need the custom scripts we will need using the amp_post_template_head hook. For this template, I am loading a Google font set, and AMP components from the official library: Sidebar (for the menu), Form (for newsletter signup). The last tag is a meta for Google Analytics.

add_action( 'amp_post_template_head', [ $this, 'scripts' ], 1 );

...

public function scripts( $amp_template ) {
	?>
	<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,300,300italic,400italic,700,700italic,800,800italic">
	<script async custom-element="amp-sidebar" src="https://cdn.ampproject.org/v0/amp-sidebar-0.1.js"></script>
	<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>
	<meta name="amp-google-client-id-api" content="googleanalytics" />
	<?php
}

Next, we will need to add our own css using the amp_post_template_css hook.

add_action( 'amp_post_template_css', [ $this, 'css' ] );

...

public function css() {
	Main::get_template_part( 'Partials/style.php' );
}

Here comes the interesting part: how to load partials. We will leverage the built-in filter amp_post_template_file that allows us to load our own main template, that will load partials into it. Let’s start loading a custom template when it is a single type

add_filter( 'amp_post_template_file', [ $this, 'custom_template' ], 10, 3 );

...

function custom_template( $file, $type, $post ) {
	if ( 'single' === $type ) {
		$file = Main::template_path( 'Partials/custom-single.php' );
	}
	return $file;
}

And this is the template custom-single.php that will be loaded:

<?php
/**
 * Single view template.
 *
 * @package AMP
 */

/**
 * Context.
 *
 * @var AMP_Post_Template $this
 */

$this->load_parts( array( 'html-start' ) );
$this->load_parts( array( 'scripts' ) );
?>

<?php $this->load_parts( array( 'header' ) ); ?>
<?php $this->load_parts( array( 'menu' ) ); ?>

<article class="amp-wp-article">
	<header class="amp-wp-article-header">
		<h1 class="amp-wp-title"><?php echo $this->get( 'post_title' ); ?></h1>
		<?php $this->load_parts( apply_filters( 'amp_post_article_header_meta', array( 'meta-author', 'meta-time' ) ) ); ?>
	</header>

	<?php $this->load_parts( array( 'featured-image' ) ); ?>

	<div class="amp-wp-article-content">
		<?php echo apply_filters( 'amp_post_article_content', $this->get( 'post_amp_content' ) ); // WPCS: XSS ok. Handled in AMP_Content::transform(). ?>
	</div>

	<footer class="amp-wp-article-footer">
		<?php $this->load_parts( apply_filters( 'amp_post_article_footer_meta', array( 'meta-taxonomy', 'meta-comments-link' ) ) ); ?>
	</footer>
</article>

<?php $this->load_parts( array( 'footer' ) ); ?>

<?php
$this->load_parts( array( 'html-end' ) );

In this custom template that we just created, we have for example the option of loading a custom menu using the built-in method $this->load_parts( array( ‘menu’ ) );

Looking at the entire custom_template method from the Frontend class, we can now see how to load all our custom partials:

function custom_template( $file, $type, $post ) {
	if ( 'single' === $type ) {
		$file = Main::template_path( 'Partials/custom-single.php' );
	}
	if ( 'menu' === $type ) {
		$file = Main::template_path( 'Partials/custom-menu.php' );
	}
	if ( 'meta-newsletter' === $type ) {
		$file = Main::template_path( 'Partials/meta-custom-newsletter.php' );
	}
	if ( 'meta-related' === $type ) {
		$file = Main::template_path( 'Partials/meta-custom-related.php' );
	}
	if ( 'footer' === $type ) {
		$file = Main::template_path( 'Partials/custom-footer.php' );
	}
	return $file;
}

This is how the entire class will look like:

<?php
/**
 * Display frontend for AMP posts.
 */

namespace Falconio\AMP;

class Frontend {

	public function __construct() {
		add_action( 'amp_post_template_head', [ $this, 'scripts' ], 1 );
		add_action( 'amp_post_template_css', [ $this, 'css' ] );
		add_filter( 'amp_post_template_file', [ $this, 'custom_template' ], 10, 3 );
	}

	public function scripts( $amp_template ) {
		?>
		<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,300,300italic,400italic,700,700italic,800,800italic">
		<script async custom-element="amp-sidebar" src="https://cdn.ampproject.org/v0/amp-sidebar-0.1.js"></script>
		<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>
		<meta name="amp-google-client-id-api" content="googleanalytics" />
		<?php
	}

	public function css() {
		Main::get_template_part( 'Partials/style.php' );
	}

	function custom_template( $file, $type, $post ) {
		if ( 'single' === $type ) {
			$file = Main::template_path( 'Partials/custom-single.php' );
		}
		if ( 'menu' === $type ) {
			$file = Main::template_path( 'Partials/custom-menu.php' );
		}
		if ( 'meta-newsletter' === $type ) {
			$file = Main::template_path( 'Partials/meta-custom-newsletter.php' );
		}
		if ( 'meta-related' === $type ) {
			$file = Main::template_path( 'Partials/meta-custom-related.php' );
		}
		if ( 'footer' === $type ) {
			$file = Main::template_path( 'Partials/custom-footer.php' );
		}
		return $file;
	}
}

Templates

Now that the Frontend class is built, we can now start adding our custom partial templates, that are under a partials folder. These are all the partials we will need:

  • Partials
    • custom-single.php
    • custom-menu.php
    • meta-custom-newsletter.php
    • meta-custom-related.php
    • custom-footer.php

Let’s start with the menu, that we are building with the amp-sidebar component. We’ll need two elements: one menu button, and the actual menu.

<?php
use Falconio\AMP\Main;
?>

<div class="amp-wp-menu">
	<div>
		<button on="tap:sidebar.toggle" role="button" class="menu-button">
			<?php _e( 'Menu', Main::TEXT_DOMAIN ); ?>
		</button>
	</div>
</div>

<amp-sidebar id="sidebar" layout="nodisplay" side="left">
	<ul>
		<li on="tap:sidebar.close" role="button" tabindex="0" class="menu-button">
			<?php esc_html_e( 'Close', Main::TEXT_DOMAIN ); ?>
		</li>

		<?php wp_nav_menu( [ 'menu' => 'menu', 'items_wrap' => '' ] ); ?>
	</ul>

</amp-sidebar>

We now want to set up a newsletter signup form in here. We can use the amp-form component that allows us to display a form and submit values, in this example using an AJAX request.

<?php
use Falconio\AMP\Main;

$post_url = admin_url( 'admin-ajax.php?action=[insert action]' );
?>

<div class="amp-wp-newsletter">

	<h2>
		<?php _e( 'Sign up for our newsletter', Main::TEXT_DOMAIN ); ?>
	</h2>

	<div class="description">
		<?php _e( 'Join 100,000+ fellow marketers and get expert insights sent straight to your inbox.', Main::TEXT_DOMAIN ); ?>
	</div>

	<form id="amp-newlsetter" name="submit" method="post" action-xhr="<?php echo $post_url; ?>" target="_top">

		<input type="email" name="email" aria-label="<?php _e( 'Email address', Main::TEXT_DOMAIN ); ?>" placeholder="<?php _e( 'Enter your email', Main::TEXT_DOMAIN ); ?>" required>

		<label>
			<input type="checkbox" name="consent" required>
			<span class="label"><?php _e( 'Accept Privacy Policy and Terms of Service', Main::TEXT_DOMAIN ); ?></span>
		</label>

		<input type="submit" value="<?php _e( 'Sign me up', Main::TEXT_DOMAIN ); ?>">

		<div class="success" submit-success>
			<?php _e( 'Good choice! We\'ll be in touch', Main::TEXT_DOMAIN ); ?>
		</div>

	</form>

</div>

We also wanted to show some related content, and add a new query in the mix. The same way that it would work on a “normal” template, we can make a query using get_posts() and loop through the results to output a grid of posts.

<?php
use Falconio\AMP\Main;

$related_posts = get_posts( [
	'post__not_in'   => [ get_the_ID() ],
	'post_type'      => 'post',
	'post_status'    => 'publish',
	'posts_per_page' => 3,
] );

?>

<div class="amp-wp-meta amp-wp-related">

	<h2><?php _e( 'Related articles', Main::TEXT_DOMAIN ); ?></h2>

	<?php foreach ( $related_posts as $related_post ) : ?>
		<article class="related-article">
			<a href="<?php echo amp_get_permalink( $related_post ); ?>">
				<amp-img
					src="<?php echo get_post_meta( $related_post->ID, 'thumbnail_image', TRUE ); ?>"
					layout="responsive"
					width="488"
    				height="332"
					alt=""
				>
				</amp-img>
				<div class="content">
					<h3><?php echo strip_tags( $related_post->post_title ); ?></h3>
					<p><?php echo get_the_time( "F jS, Y", $related_post ); ?></p>
				</div>
			</a>
		</article>
	<?php endforeach; ?>

</div>

Finally, we wanted to add a custom footer with links to our privacy pages:

<footer class="amp-wp-footer">
	<div>
		<a title="Privacy policy" href="https://www.falcon.io/privacy-policy/">Privacy</a> ·
		<a title="Cookie policy" href="https://www.falcon.io/cookie-policy/">Cookies</a>
	</div>
</footer>

By now, we have set up a fully custom template, leveraging the templating engine of the AMP plugin, and loading our own partials. Started from the bottom, now we here.

Analytics & Search Console

My parting words will be about how to set up Google Analytics (which you will probably want to do) and make sure that your AMP pages are valid using Search Console.

For Google Analytics, it’s pretty easy. You have an Analytics tab under the AMP menu in the WordPress admin, where you’ll need to add a JSON configuration like the following that will for example send pageviews to Google Analytics:

{
	"vars": {
		"account": "UA-XXXXX-Y"
	},
	"triggers": {
		"trackPageview": {
			"on": "visible",
			"request": "pageview"
		}
	}
}

To make sure that your pages are valid following the AMP syntax, you can use the AMP Validator to check your markups as soon as possible. Afterwards, once on production and live, you will be able to check the status of your pages with Search Console, where under the AMP tab, you will find if your pages are valid or not.

————–

That’s all folks. Thanks for the long read, and please feel free to reach out in the comments for questions or suggestions!

Leave a comment

Your email address will not be published. Required fields are marked *