9

Does anyone have any thoughts on automating the implementation Google's new AMP article markup?

https://www.ampproject.org/docs/get_started/create/basic_markup.html

Also see: Google will start sending search traffic to fast-loading AMP news articles in February 2016

Thanks!

3 Answers 3

15

You're asking kind of a loaded question but as far as Craft is concerned, you're in a good position to spit out exactly what you need as far as markup and styles go. AMP is more or less just a small subset of HTML.

When compared to something like WordPress, Craft is going to give you all the tools necessary to spit out an AMP page just by virtue of it doesn't dictate layout and styling nor include any extra fluff. And it provides easy routing features to make it easy to make different "versions".

Different Versions / Different Routes

Depending on how your site is setup, you'll want different versions of your content—one regular and one for AMP... not unlike the old m dot sites. (Taking it back to the old school WAP era I guess...)

Let's say you have a section called News. If your entries URL Format looks like: http://site/news/slug/, you might want to have the AMP version look like http://site/news/slug/amp.

One way to do that in Craft is to setup a route. You can do that in Settings -> Routes -> New Route.

enter image description here

That will load the _amp.html template inside a news folder. You'll have to do a little extra work to tell Craft where to pull the entry from since routes only load templates by default. Since we used the slug variable in our routes, we can use that to grab the entry from the news section and also set the entry variable.

{% extends "_layoutamp" %}

{% set entry = craft.entries.section('news').slug(slug)[0] %}

{% block content %}
  ... rest of your content
{% endblock %}

The AMP docs start with a simple skeleton. I used {% extends "_layoutamp" %} in our _amp template so we need to make a ``_layoutamp.html" file to match inside Craft's templates folder.

   {# _layoutamp.html #}
    <!doctype html>
    <html amp lang="en">
      <head>
        <meta charset="utf-8">
        <title>{{entry.title}}</title>
        <link rel="canonical" href="{{entry.url}}" />
        <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
        <script type="application/ld+json">
          {
            "@context": "http://schema.org",
            "@type": "NewsArticle",
            "headline": "{{entry.title}}",
            "datePublished": "{{entry.dateCreated.iso8601()}}",
            "image": [
              "logo.jpg"
            ]
          }
        </script>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
        <script async src="https://cdn.ampproject.org/v0.js"></script>
      </head>
      <body>
        {% block content %}
           {# brings in the content from the _amp.html content block #}
        {% endblock %}
      </body>
    </html>

AMP recommends some metadata near the top, in the form of JSON. Easy enough to spit out the required fields in Twig.

"Linking" to an AMP page

You'll notice a <link rel="canonical" href="{{entry.url}}"> in that file as well to tell Google your main version is here.

On your "normal" page, you'll want to add a reference to the AMP page too. (I also like to set the canonical version as well for good measure.

<link rel="canonical" href="{{entry.url}}">
<link rel="amphtml" href="{{entry.url}}/amp">

That will tell Google the AMP version is at the URL.

Anything else?

Pretty much the rest is up to you. AMP is pretty easy to setup but you still need to use their custom tags.

Most notably, things like images and videos need to use custom tags like amp-img and amp-video, so the AMP runtime can intercept the loading of those assets and handle them.

Will be interesting to see how this affects page rank moving forward. Google confirmed it's not a ranking signal (yet) but that doesn't mean it won't be in the future.

6
  • 1
    Thank you for the awesomely detailed response. Will try my best to implement this!
    – RDMD
    Commented Dec 15, 2015 at 11:55
  • 1
    Let me know how it works out. I'm actually going through the process of rebuilding our own site, you gave me a push to check it out so thanks for the reminder. Commented Dec 15, 2015 at 16:17
  • Sure thing! Will do. I think to make this easier, existing SEO plugins available for Craft could work toward include this within their scope for both Facebook Instant Articles & for Google AMP.
    – RDMD
    Commented Dec 16, 2015 at 6:26
  • Updated my answer a bit; they've changed the default boilerplate since. Commented Mar 11, 2016 at 16:22
  • 1
    I love that stackexchange has people like you @RitterKnight. Thanks for this awesome answer!
    – Bryce
    Commented Jan 14, 2021 at 17:34
3

To revitalize this valuable post and expand a bit on what the accepted answer covers:

I've done this using the same template (with conditionals).

Since you're going to have to handle all of your routes manually anyways, I've set up a controller (in a plugin) to give me a little more control over what data these pages get.

By wrapping the renderTemplate method in my own, I'm adding context that can be used in templates to control how things are output.

private function renderAmp($template, array $variables = array()){
        craft()->config->set('amp','');
        if($variables['amp'] == 'amp'){
            craft()->config->set('devMode',0);
            craft()->config->set('siteUrl',craft()->config->get('siteUrl').'/amp/');
            craft()->config->set('amp','/amp');
        }

        $this->renderTemplate($template, $variables);
    }

I use that config to generate a URL for entries that can be used globally. It sits in my plugin's service and looks something like this:

public function getEntryUrl($entry){
        $channel = craft()->sections->getSectionById($entry->sectionId);
        $date = str_replace('-', '/', $entry->postDate);
        $slug = $entry->slug;

        $url = implode('/',
            array(
                craft()->config->get('amp'),
                $channel,
                $date,
                $slug
            )
        );

        return strtolower($url);
    }

And for completion's sake, my routes & some useful stuff from my templates. Also, note -- I chose to prefix my slugs with amp instead of adding it to the end. Mostly preference, I guess.

Routes:

// Primary
$routes = array(

    // News
    '(?P<channel>news)/(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>.*)/?$' => array('action' => 'pluginName/newsEntry'),
    '(?P<channel>news)/?$' => array('action' => 'pluginName/newsAll'),

    // Home
    '(?P<slug>.*)?/?$' => array('action' => 'pluginName/home'),

);

// Amp
$routes = array_combine(
    array_map(function($k){
        $pre = '^(?P<amp>amp)?/?';
        return $pre.$k;
    }, array_keys($routes)),
    $routes
);

return $routes;

Templates:

{% if amp is empty %}

    {% set htmlTag = 'xmlns="http://www.w3.org/1999/xhtml" lang="en-US"' %}

    {% set head %}
        <link rel="canonical" href="/{{ matches[0] }}">
        <link rel="amphtml" href="/amp/{{ matches[0] }}">
        <link rel="stylesheet" href="/css/main.css">
    {% endset %}

    {% set scripts %}
        <script type="text/javascript" src="/js/main.js"></script>
    {% endset %}

{% else %}

    {% set htmlTag = 'amp' %}

    {% set head %}
        <link rel="canonical" href="/{{ matches[0]|slice(4) }}">
        <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">

        <style amp-boilerplate></style>

        <script async src="https://cdn.ampproject.org/v0.js"></script>

        <style amp-custom>
            {{ source('html/css/amp.css')|raw }}
        </style>

        <link href='https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css' rel='stylesheet' type='text/css'>
    {% endset %}

    {% set scripts = '' %}

{% endif %}

// To output an entry's proper URL in a template:
craft.pluginName.getEntryUrl(entry)
3

I wrote the SEOmatic plugin -- AMP is really just a name for some best practices for web development that speed things up on mobile. There isn't anything terribly special about it, I do most of what they list in their spec for websites already.

The "meta tags" that they want you to add are just some JSON-LD, which you can easily add on your own, or via SEOmatic's renderJSONLD function.

As for implementing AMP, you might find this helpful: Google AMP: Should You Care?

3
  • 3
    AMP is a little more than just some best practices for mobile. It actually is a subset of HTML5 using custom tags like amp-img so can it can control loading. Your plugin is helpful though! Commented Mar 11, 2016 at 16:22
  • Yes, AMP is DEFINITELY more than just methodologies. It's a very comprehensive library and as @RitterKnight mentioned a subset of HTML/CSS (and JavaScript/CSS; yes there are restrictions): ampproject.org/docs/reference/components It also requires things like limiting your CSS & injecting it directly onto the page, which is - at best - debatably a best-practice unless you're operating in a cached environment like AMP. Commented Dec 23, 2017 at 0:06
  • Well, what I meant was that the things that make AMP fast are methodologies that you can implement yourself without AMP. AMP just enforces them, and provides functionality as you mentioned to make it easier to do. Commented Dec 31, 2017 at 18:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.