AMP

The AMP Camp build process

Websites

What’s AMP Camp?

It’s one thing to work as a Developer Advocate and talk about how AMP can be used to create quick, interactive sites. It’s quite another to actually build one! So a few of us decided to create a simple but fully functional e-commerce website, covering everything until the checkout. We hoped to learn the subtleties involved in creating these interactions, to create guidance for others, and to make some nice feature requests.

And, thus, the AMP CAMP demo site was born!

https://blog.amp.dev/wp-content/uploads/2020/03/AMP-Camp-blog-post-video-compressed-by-Paul-1-2.mp4

We learned a whole lot making this site, and we want to share this with you. In this series of articles, we’ll explain how we built the site and created its interactions. You can also watch the talk at the end of this post.

First things first: let’s start with an article about the AMP Camp build process.

Why did we use a build process?

It’s common to compile software from a set of source files into the files that are used in production. This is called a build process. Sometimes it’s convenient to do this with HTML – to create resources in a more convenient form, then automatically compile that down to the HTML that gets served to browsers.

A build process is especially convenient with AMP. After all, the information required to render an AMP webpage lives in a single file. Its CSS is included inside the HTML in <style> tags. And if you’re not using <amp-script>, you won’t even be providing separate JavaScript resources. In contrast, almost all non-AMP webpages refer to many other CSS and JS files. AMP is simpler!

Nonetheless, while you’re creating your AMP pages, you probably won’t want to create each page within a single file unless your site is very simple. At the very least, unless you’re using you’re combining CSS and HTML intentionally in JSX or web components, it’s messy to work with source files that contain both CSS and HTML. You’ll likely at least want to create CSS and HTML in separate files, then use a build process to combine them.

Thinking about this further, your webpages will usually share much of the same HTML. For example, they may have the same navigation menu, or similar footers. It’s simply not a best practice to copy identical HTML from page to page. What if you want to change that menu? You’d have to copy and paste the new HTML across your site! That menu should live in its own file instead. More generally, you want to build pages from HTML partials. Since AMP is an HTML framework, in which HTML describes user interactions and can even contain logic, this holds doubly true.

Similarly, it’s rarely best to write all your CSS in one jumbo-size file and make every page load it. You might argue that, once this large file loads, it will likely be cached, so as the user progresses through your site, they won’t experience that 200K render-blocking hit on each page. But if that first page loads slowly, the user may never get to that second page! You really want each page to load a smaller CSS file, containing mostly only the styles used on that page.

You can’t use more than 75K of CSS if you want your AMP page to pass validation. Plus, that CSS must be contained inside the HTML to save a render-blocking network call! Hence, it’s best to only add the CSS to a page that is needed. All this is much easier if you use a build process to assemble your CSS from partials, then merge that into each HTML file.

So, just as it’s often easier and more convenient to assemble webpages from smaller parts with a build process, it’s easier and more convenient for AMP web pages. As a bonus, plenty of build tools have been created specifically for AMP, either to make AMP pages easier to create, or to make them faster for users! To learn more, let’s take a look at the build process used in the AMP Camp demo site.

The AMP Camp site is built with node.js and gulp. Throughout this article, we’ll refer to the code on github, especially the gulpfile. We’ll also refer to ways in which you can make your build process better than ours!

Building our HTML

const fileinclude = require('gulp-file-include');

AMP Camp builds its HTML from partials, in much the way discussed above. Since our site is relatively simple, we used fileinclude. That said, if we expand the project just a little, we’ll probably want to migrate to a more powerful tool.

To see this in action, check out the start of index.html, which includes separate partials for the <head>, the page header, the navigation menu, and some other things:

<!doctype html>
%%include('../partials/copyright.html')
<html ="" lang="en">
<head>
    %%include('../partials/head.html', {
        "pageType": "index"
    })
</head>
<body>
    %%include('../partials/header.html', {
        "pageType": "index"  
    })
    %%include('../partials/menu.html')

Some of these partials contain other partials. Notably, we decided to put our analytics in its own partial, analytics.html, which header.html then imports.

Building our CSS

AMP Camp’s head.html partial is built from subpartials as well. These subpartials are not HTML files, but CSS files. head.html includes CSS files that are common to each page:

<style amp-custom>
  %%include('../../css/global.css')
  %%include('../../css/header.css')
  %%include('../../css/menu.css')
  %%include('../../css/footer.css')
  %%include('../../css/social.css')

as well as CSS that’s specific to each page:

%%if (context.pageType == 'index') {
  %%include('../../css/index.css')
}

To build more efficient CSS, you can divide your HTML into smaller partials and subpartials and associate the appropriate CSS with each. This way, each page of the site truly only contains the CSS it needs. For example, amp.dev organizes its CSS with atomic design, a system in which larger UI design elements contain progressively smaller ones, all the way down to “atoms”. Check it out here! Another option is to use a framework like Next.js, which lets you do a React-style CSS/HTML association. (We encourage you to learn about Next.js’ AMP support here – or try this tutorial.)

const sass = require('gulp-sass');

AMP Camp builds its CSS from SASS, a popular higher-level language that compiles down to CSS. This works with AMP pages just as it does with non-AMP pages!

However, if your SASS happens to contain extended characters, the SASS processor will automatically output a rule to specify the character encoding:

@charset "UTF-8";

Unfortunately, such rules aren’t standard for CSS that lives inside a <style> tag. Thus this rule inspires the AMP validator (see below) to throw an error!

Our solution isn’t pretty, but it’s simple and robust: after SASS does its work, we postprocess the resulting CSS to remove any such rules:

gulp.task('styles', function buildStyles() {
    const cssEncodingDirective = '@charset "UTF-8";';

    return gulp.src(paths.css.src)
        .pipe(plumber())
        .pipe(sass(options.env === 'dist' ? { outputStyle: 'compressed' } : {}))
        .pipe(options.env === 'dev' ? replace(cssEncodingDirective, '') : noop())
        .pipe(autoprefixer('last 10 versions'))
        .pipe(mergeMediaQuery({log: true}))
        .pipe(gulp.dest(paths.css.dest));
});

AMP-specific tools

const gulpAmpValidator = require('gulp-amphtml-validator');

Speaking of AMP validation, we want to ensure that our pages are always meeting AMP’s standards, so that they’ll be eligible for AMP caches, and so we’ll avoid errors, but also because we want to serve fast, accessible pages. Consequently, our build process includes the AMP Validator. We’ve set it up so that when any page isn’t valid AMP, the build breaks.

gulp.task('validate', function validate() {
    return gulp.src(paths.html.dest + '/**/*.html')
        .pipe(gulpAmpValidator.validate())
        .pipe(gulpAmpValidator.format())
        .pipe(gulpAmpValidator.failAfterError());
});

If we didn’t want any invalid page to break the build, we could omit the failAfterError() step above.

Note that some of AMP Camp’s pages are rendered from templates on the server. We don’t want to run the validator in the server! Fortunately, since what we substitute into those templates is simple and highly predictable, we feel confident running the validation on these templates. In a larger project, it would be safer to render some sample pages during the build process and test those for validity.

const ampOptimizer = require('@ampproject/toolbox-optimizer');

Finally, we use the AMP Optimizer for a number of purposes!

  • Many AMP components require their own small script to function. The developer needs to keep track of these for each page and ensure each is included in the <head>. That’s why, for this project, we developed the amphtml-autoscript gulp module, which detects which components a page uses and automatically inserts the necessary scripts! This functionality has now been incorporated into the AMP Optimizer.
  • AMP pages contain boilerplate that hides the page until the AMP runtime loads, gets parsed, and executes. When it starts up, the AMP runtime performs various transformations on the DOM, then unhides the page. AMP caches perform these transformations in advance, serving the modified HTML right to your browser and speeding things up substantially. The AMP Optimizer does this right on your origin! This can make AMP pages twice as fast on your origin, so we think it’s invaluable.
  • Finally, it automatically inserts mandatory AMP tags so we don’t have to.

AMP Camp runs the AMP Optimizer during the build process on our compiled templates. This means our template delimiters can’t contain angle brackets, like <% and %>, because the Optimizer would parse these as HTML. We use square brackets ([% and %]) instead.

AMP Optimizer can be used in your server as well, at runtime. If you do this, you should cache optimized pages to avoid excess server load.

And there you have it

We’re hoping the build process we used for AMP Camp helps make your own AMP project easier! Again, to see a build process for a larger site, check out the way amp.dev is built. Or, to see an interesting React-based approach, check out the Concert sample or Article sample.

Stay tuned for the next article in the AMP CAMP series – how we used templates on the client and on the server.

Happy building!

Written by Ben Morss, Developer Advocate

More room for more style

Websites

Increasing the AMP CSS limit

Whether your style is a professional journalist website or bohemian ecommerce emails, AMP now gives you more room to show it. The AMP maximum allowable CSS limit has been raised to 75kB – a 50% increase! This is the biggest, and, well, only change to the CSS cap in the near half-decade lifespan on the AMP project, and affects both AMP powered websites and AMP for Email.

Why

AMP had the 50kB limit with the best intentions – a forcing function to encourage strong CSS hygiene. Over the years, we’ve received a lot of feedback on the CSS allotment, and we hear you! Fitting experiences that meet higher-than-ever user expectations within a maintainable 50kB limit is difficult. 

This was no hasty change – we investigated many different possibilities, including automatic AMP cache level minification, migrating to a percentage-of-used-selector based limit, or even a dynamic limit based on document complexity. Ultimately, raising the limit was the option that best fit AMP developer’s needs.

What’s next

So the good news is that you now have 75kB of CSS available on each AMP powered webpage and AMP email. Even better news: you don’t have to change a thing on your existing AMP experiences! Since the AMP library is evergreen, this limit will automatically roll out to any and every AMP powered site soon. 

The AMP team is thrilled to see what new possibilities this change will open up for all developers, and can’t wait to see what’s dreamed up. You can join the community to share what you make, or just follow along with all AMP updates, on the Slack channel, twitter, or by sign up for our newsletter.

Crystal Lambert, Technically a Writer, Google, AMP Project

Patrick Kettner, Developer Cheerleader, Google, AMP Project

Easier AMP development with the new AMP Optimizer

Websites

Creating AMP pages has just gotten a lot simpler with the new AMP Optimizer 2.0. 

The primary goal of AMP Optimizer is to make AMP pages load even faster by applying additional server-side optimizations. With this release, AMP Optimizer also makes it a lot simpler to integrate AMP into frameworks and CMSs! For example, we’ve just released an AMP plugin for the wonderful static site generator Eleventy that makes use of all these new features. 

But first let’s take a quick look at what’s new in AMP Optimizer 2.0:

  1. Auto AMP component script import. 
  2. Auto add any missing mandatory AMP tags.
  3. New Markdown support via <img> tag to <amp-img> conversion.
  4. CSP tag generation for inline amp-scripts.
  5. Built-in HTML minification removing unneeded whitespace (this includes AMP specific optimizations such as removing whitespace from inline JSON and minifying inline amp-scripts using terser).
  6. AMP server-side-rendering now supports the intrinsic layout. This means AMP Optimizer can remove the AMP boilerplate for pages using the intrinsic layout resulting in much faster load times.
  7. Transformations run 40% faster by switching from parse5 to htmlparser2!

Now, let’s talk about the first three features and how they simplify building AMP pages and integrating AMP into Frameworks and CMSs.

Auto AMP component extension import

Auto AMP component import means that AMP Optimizer will analyze the DOM for any used AMP components and will automatically inject all needed scripts imports into the head.

For example, if it encounters the following tag:

<amp-video src="video.mp4" width="300" height="200"></amp-video>

It will automatically add the amp-video script extension import:

<script async custom-element="amp-video"   src="https://cdn.ampproject.org/v0/amp-video-0.1.js"></script>

This works also for AMP components which don’t require a specific tag:

<div amp-fx="fade-in">I will fade in</div>

In this case, the presence of the amp-fx attribute will trigger the import of the amp-fx-collection script:

<script async custom-element="amp-fx-collection" src="https://cdn.ampproject.org/v0/amp-fx-collection-0.1.js"></script>

This is pretty awesome, because many AMP features are now super straightforward to use and no longer require you to go to the AMP docs and copy/paste the script import. We’re expecting to see a significant drop in amp.dev traffic once this feature has been widely adopted!

Auto add any missing mandatory AMP tags.

The other piece of the puzzle for how AMP Optimizer improves developer experience is the ability to add any missing mandatory AMP tags. For example, given the following HTML fragment:

<html>
<head>
  <title>My Page</title>
  <link rel="canonical" href="/mypage.html" />
</head>
<body>
  <h1>Hello World!</h1>
</body>

AMP Optimizer will automatically add all the mandatory tags and attributes needed to turn this into a valid AMP page:

<!doctype html>
<html >
<head>
  <meta charset="utf-8">
  <title>My Page</title>
  <link rel="canonical" href="/mypage.html" />
  <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
  <style amp-boilerplate>...</noscript>
  <script async src="https://cdn.ampproject.org/v0.js"></script>
</head>
<body>
  <h1>Hello World!</h1>
</body>
</html>

This means, you don’t need to include any AMP specific markup in your layout templates when using AMP Optimizer. Together with auto component import, creating AMP pages becomes as easy as using built in HTML tags.

Working towards improved AMP support for Frameworks and CMSs 

This AMP Optimizer release is the first stepping stone in offering better AMP support in frameworks and CMS platforms. 

Next.js already offers great AMP support and has AMP Optimizer already built in. This means that with this AMP Optimizer release (available in Next.js v9.2.2), creating AMP pages in Next.js has become even easier. You can now directly use AMP components (in this case amp-fx-collection):

export const config = {amp: true};

export default () => (
 <div amp-fx='fade-in'>I will fade-in</div>
);

Without having to explicitly import the amp-fx-collection-.0.1.js component script via the <Head>:

import Head from 'next/head'
export const config = {amp: true};

export default () => (
  <>
    <Head>
      <script
          async
          key="amp-fx-collection"
          custom-element="amp-fx-collection"
          src="https://cdn.ampproject.org/v0/amp-fx-collection-0.1.js"
        />
    </Head>
   <div amp-fx='fade-in'>I will fade-in</div>
  </>
);

Markdown Support

This AMP Optimizer release adds a new markdown mode, which will convert <img> tags into <amp-img> or <amp-anim> tags (all other Markdown features are already supported by AMP).

This enables the following conversion flow:

README.md => HTML => AMP Optimizer => valid AMP

Combined with automatically AMP component script import and automatically adding missing mandatory AMP tags it’s now possible to directly convert markdown into valid AMP:

const AmpOptimizer = require('@ampproject/toolbox-optimizer');
const md = require('markdown-it')({
  // don't sanitize html if you want to support AMP components in Markdown
  html: true,
});

// enable markdown mode
const ampOptimizer = AmpOptimizer.create({
  markdown: true,
});

const markdown = `
# Markdown 

Here is an image declared in Markdown syntax: 

![A random image](https://unsplash.it/800/600).

You can directly declare AMP components:

<amp-twitter width="375" 
             height="472" 
             layout="responsive" 
             data-tweetid="1182321926473162752">
</amp-twitter>

Any missing extensions will be automatically imported.
`;

const html = md.render(markdown);

// valid AMP!
const amphtml = await ampOptimizer.transformHtml(html, {
  canonical: filePath,
});

Summary

Every website publishing AMP pages should use AMP Optimizer to benefit from the performance improvements of up to 50% faster rendering times. The best part: performance will automatically get better over time with every new server-side optimization that is released. 
The new features introduced with this release greatly improve the AMP developer experience. Using AMP components becomes as straightforward as using any other HTML tag. But this is only the first step and we plan to build upon this in the future and bring AMP support to more frameworks and CMS Stay tuned to the AMP Blog and follow @AMPhtml on Twitter for any future news on the AMP Optimizer.

Written by Sebastian Benz, Developer Programs Engineer, AMP Project, Google

Learn how to use the Search Console AMP status report

Websites

The following guest post was written by Daniel Waisberg, Search Advocate, Google

Search Console is a free tool provided by Google to webmasters and developers to help optimize sites for search. In addition to data about search performance, index coverage and structured data markup, the tool can also help improve your AMP implementation. It shows AMP issues Google discovered in your website, provides information on how to fix them, and allows you to ask Google to validate your fixes. 

We’ve released a new episode of our Search Console Training YouTube series that shows you how to use the AMP status report in the tool.

In a nutshell, the AMP status report top level view shows crawled AMP pages with any issues found by Google. You’ll find a table with all issues sorted by a combination of severity, validation state, and number of pages affected. Click a specific issue to see more details and   a link to learn more about how to fix it and a process to notify Google about your fixes. You’ll also find a list of example URLs affected by this issue – you can click a specific URL to find the HTML and exact location of the offending code. 

It might also be a good idea to use the AMP Test tool, where in addition to testing a specific page, you can also upload a piece of code to check for issues – this can be helpful to debug issues for new pages. This tool will check your general AMP implementation, without specific information about Google indexing, so there is no need to verify your website to use it.

We hope you enjoy the videos and it helps you improve your AMP implementation! If you’re not using Search Console, check out the options available to verify your site ownership. For questions or comments, reach out via the Webmasters Help Community or our Twitter account

In search of the amp.dev search

Websites

Editor’s Note: the following guest post was written by Thorsten Harders and Sebil Satici, Developers at Jung von Matt.

Amp.dev offers a lot of resources that make it easier to get started and build with AMP: case studies, details on how each component works, guides with best practices, step-by-step tutorials and plenty of executable code examples. In fact, it offers so many resources that we needed a way for developers to quickly discover and reach all that existing content. We wanted to do so by making all of our content searchable directly on the site. This article walks through the steps we took to implement search on amp.dev.

https://blog.amp.dev/wp-content/uploads/2020/02/ezgif.com-gif-to-mp4.mp4

TL;DR: The new amp.dev search is built using <amp-lightbox>, <amp-list>, <amp-autocomplete>, <amp-mustache> and of course <amp-bind> to glue them all together. It uses a custom API that is backed by Google custom search and it uses Service Worker functionalities to cache the latest search query and results.

Built with AMP components

We had three goals when building the amp.dev search functionality:

  1. The search should be accessible in its own layer from all pages.
  2. The search should be optimized for finding AMP components, as these are core to the AMP developer experience.
  3. The search should be implemented using valid AMP functionalities.

Hide and show the search layer (with amp-lightbox)

Looking around the web you’ll see many different types of search functions. Some are just plain input fields while others expand and collapse with fancy animations. Some trigger a page-load to paginated result pages, others asynchronously display results. Some auto-suggest search terms, others don’t. For amp.dev we wanted to combine the best of those approaches to come up with a solution that manages to stay out of the user’s way as much as possible while at the same time being quickly accessible. Therefore, we decided to encapsulate the whole search in a fullscreen layer to:

  • suggest interesting articles to users (e.g new guides or components that have been published) no matter what page the user is on
  • avoid distracting the user with other elements on the page
  • quickly display the search results inline without having to load an extra results page

To accomplish our needs we have decided to continue with <amp-lightbox>. It makes it easy for us to hide and show the search layer while offering us helpful actions and events we could use for the integration in our AMP frontend.

Adding seamless interaction

From a user experience perspective, it was particularly important to us that the user achieves a seamless transition between entering the search query and seeing the result. When the user opens the search layer, the input field is automatically focused and the user can start typing right away. When the search layer is closed, we focus the search toggle again, so that keyboard users can continue in the same position as before.

We implemented this feature using the <amp-lightbox> open and close events in combination with the global focus action:

<amp-lightbox layout="nodisplay"
      on="lightboxOpen:searchInput.focus;
          lightboxClose:searchTriggerOpen.focus"
      scrollable>

Listing the search results (with amp-list and amp-mustache)

For displaying the search results on the page, we use the <amp-list> component as it has paging and infinite scroll already built-in – exactly what you need when listing search results.  The actual search is implemented server-side and can be accessed via an API endpoint /search/do which returns a JSON object similar to this:

{
  "result": {
    "pageCount": 10,
    "pages": [
      {
        "title": "Some title",
        "description": "Description",
        "url": "http://amp.dev/some/link"
      },
      ...
    ]
  },
  "nextUrl": "/search/do?q=amp&page=2"
}

We use amp-bind to update the full search URL in our  <amp-list>, by binding the [src] attribute to the input query. To enable infinite scrolling, we set the load-more attribute and for a cleaner reload experience when triggering subsequent searches, we set the reset-on-refresh attribute. In the <amp-mustache> template we use the data from the result object to render the list dynamically. Here is the code:

<amp-list id="searchList"
          src="/search/initial-items"
          [src]="query ? '/search/initial-items : '/search/do?q=' + 
encodeURIComponent(query)"
          binding="no"
          items="."
          height="80vh"
          layout="fixed-height"
          load-more="auto"
          load-more-bookmark="nextUrl"
          reset-on-refresh
          single-item>
  <template type="amp-mustache">
    <div class="search-result-list">
      {{#result.pages}}
      <a href="{{url}}">
        <h4>{{title}}</h4>
        <p>{{description}}</p>
      </a>
      {{/result.pages}}
    </div>
  </template>
</amp-list>

Suggest what matters (with amp-autocomplete)

https://blog.amp.dev/wp-content/uploads/2020/02/ezgif.com-gif-to-mp4-1.mp4

According to our analytics data, developers most often visit amp.dev to access AMP component documentation and samples. That’s why we wanted to make these as easy to discover as possible. With <amp-autocomplete>, AMP offers an out-of-the-box solution for implementing auto-suggestions. The goal is to auto-suggest all available AMP components. The static datasource is provided by the /search/autosuggest endpoint and the autocomplete generates suggestions client-side using the filter="substring" designation.

<amp-autocomplete filter="substring"
                  min-characters="1"
                  on="select:AMP.setState({    query: event.value })"
                  submit-on-enter="false"
                  src="/search/autosuggest"
>
    <input placeholder="What are you looking for?">
</amp-autocomplete>

Executing the search (with amp-form)

Users can trigger the search by selecting one of the auto-suggested options or submitting the form. To execute the actual search request we are using the state query as a URL parameter.

<amp-list [src]="'/search/do?q=' + encodeURIComponent(query) + '&locale=en'">

Based on the on action in the amp-autocomplete, the query is updated (and amp-list rerenders) both when the form submits and when autocomplete emits a select event.

<form action-xhr="/search/echo"
      on="submit:
          AMP.setState({ query: queryInput }),
          searchResult.focus,
          searchList.changeToLayoutContainer"
      method="POST" target="_top">
  <amp-autocomplete filter="substring"
                    min-characters="1"
                    on="select:AMP.setState({ query: event.value })"
                    submit-on-enter="false"
                    src="/search/autosuggest"
  >
    <input id="searchInput"
           placeholder="What are you looking for?"
           on="input-throttled:AMP.setState({ queryInput: event.value })"
    >
    <button disabled [disabled]="!queryInput">Search</button>
  </amp-autocomplete>
</form>

When the form is submitted we also focus the search result container to let the keyboard navigation start directly with the first entry. Additionally, we call changeToLayoutContainer of the <amp-list> to ensure the list’s height will change according to the content and can get smaller. Because in our case the result of the form submit event is not needed, we simply point to an echo action. Sidenote: this won’t be needed in the future as it’s soon going to be possible to use `amp-autocomplete` without  a form.

Caching previous search results via Service Worker

After we launched the first version of the search we received a related feature request fairly quickly: keep showing the results for the latest search query, even when the user navigates to another page.

To achieve this, we built upon AMP’s one-line Service Worker amp-sw which offers basic PWA functionalities like caching and offline pages. We extended it to store the latest search query and the corresponding search results.

When a search is started, we display the previous search query and its results. Otherwise, we will display a list of suggested articles. On page load, we initialize an amp-state object from a server endpoint /search/latest-query which populates the search input field and the search results:

// search.html
<amp-state id="query" src="/search/latest-query"></amp-state>

<amp-list src="/search/initial-items"
          [src]="query ? '/search/initial-items : '/search/do?q=' + encodeURIComponent(query)"
...
</amp-list>

The trick is: this server-endpoint does not exist. The magic happens in the Service Worker which intercepts the route and creates a new response with the cached search query and search results from the user’s last search request and sends it back to the page instead of loading the original response from the network.

To save the latest search query, we grab the query parameter from the requested URL with a regular expression and store it in a newly created response object in our cache.

Then the route handler checks if there is an entry in the cache that matches the search request. If there is the results are returned from the cache immediately. Otherwise, the request falls through to the server and then gets cached for the following calls.

// serviceworker.js
async function searchDoRequestHandler(url, request) {
  const searchQuery = decodeURIComponent(url.search.match(/q=([^&]+)/)[1]);
  const cache = await caches.open(SEARCH_CACHE_NAME);


  cache.put(SEARCH_LATEST_QUERY_PATH, new Response(`"${searchQuery}"`));

  let response = await cache.match(request);
  if (response) return response;

  response = await fetch(request);
  if (response.status == 200) {
    cache.delete(request, {
      ignoreSearch: true,
    });
    cache.put(request, response.clone());
  }

  return response;
}

This way when the user opens the search layer on another page, they automatically receive their previous search results back and can continue where they left off.

Here you can see how a handler function is registered:

// serviceworker.js
self.addEventListener('fetch', (event) => {
  const requestUrl = new URL(event.request.url);
  if (requestUrl.pathname === '/search/do') {
    event.respondWith(searchDoRequestHandler(requestUrl, event.request));
  }
});

Intercepting and dynamically changing requests with the help of the Service Worker API is a neat way whenever you want to personalize data used by AMP components that load data from remote endpoints like <amp-state> or <amp-list> et al. Just like it helped us to enhance the user experience for the search by caching the user’s latest search query.

Conclusion

With the implementation of a search function within amp.dev, we accomplished our goal of allowing users to precisely navigate the content of the site in an intuitive and efficient manner. For even better user experience, we are also caching previous search results with Service Worker functionalities.

The cool thing about this is that we integrated the search without a single line of JavaScript (except the Service Worker part). Just by making use of AMP’s existing components we could integrate useful features like auto-suggestion and infinite scrolling, which would be quite challenging to implement otherwise!

Written by Thorsten Harders and Sebil Satici, Developers at Jung von Matt

AMP 2019 Decoded: Thank You for an Incredible Year

Websites

2019 was a big hit for AMP. We continued to grow our community and create excellent experiences across websites, email, stories and ads. So let’s flashback to the beginning of this year and recap all the milestones we accomplished together.  Watch the video and read below:

Next.js for AMP 

Building AMP pages through React server-side rendering has gained enough momentum for us to realize we needed to add to our capabilities to support this use-case. That’s why we integrated with Next.js, one of the most popular frameworks for React. Fun fact: nextjs.org is built entirely with AMP!

amp-script

The game-changer <amp-script> finally hit your screens this year. This highly anticipated component enables you to add custom JavaScript to your AMP pages, share code across AMP and non-AMP pages, and build things that were not possible before.

OpenJS foundation welcomes AMP 

We revealed what’s next for our open governance model by announcing our decision to join the OpenJS Foundation. Its mission aligns with ours, and it’ll magnify diverse voices in tech and support our efforts to create a fairer web. 

AMP in your inbox 

Gmail launched support for AMP for email, bringing our user-first experience to an inbox near you. Email senders can now create frictionless experiences and interactive content to boost engagement. The AMP for email spec also has engagement from other major email providers like Outlook and Yahoo Mail, and we are looking forward to reshaping the email ecosystem together. 

The new component is also available on Mail.ru, and early adopters are already celebrating great results. OYO, India’s largest hospitality company, saw a 60% uplift in conversions when testing AMP for email. 

Arigato for making AMP Conf a success  

Our team met nearly 500 of you in Tokyo at our third annual #AMPConf. In addition to product announcements, we also launched a completely redesigned amp.dev, one website for all of our code samples, templates, and documentation. AMPConf2020 is already in the making, so stay tuned to hear where we will be taking the event next.

AMP Contributors Summit in the Big Apple 

In early October we caught up with around a hundred contributors in New York at the annual #AMPCS2019 to discuss new features for AMP. It wasn’t just talk, we hosted breakout sessions where we worked together to improve our projects, and knocked some AMP socks off in the process. 

A strong community

Nearly 1,000 people have contributed code to AMP and are helping us design a faster, better web for all. This year alone, 341 contributors made over 18,300 contributions across all AMP project repositories. 

The People Behind the Code

The community plays a massive role in making AMP great; it’s where we share what we know and learn what we don’t. So we launched the ‘People Behind the Code‘ series to understand how community members use AMP in real life to make a difference. 

19 new Roadshows under our belt 

AMP hit the road 19 times this year, taking our Roadshows around the globe. From Johannesburg to Seoul, 1,600 of you came out to see what AMP is all about. 

The Roadshows are designed to help developers build full-fledged AMP websites with confidence. So we deep-dive into the key four pillars: design, interactivity, DevOps and monetization, to make sure you have everything you need to excel. 

If you want to see where we will be going in 2020, head over to our AMP Roadshow page to find out. Can’t see your city on the list? Let us know, we’re always looking for new places to visit, or host your own event. We’ll provide you with all the material – all you need to do is to show up!

2020 vision 

It’s been a remarkable year. Thank you for your creativity and commitment to creating a faster, user-first web for all. We’re excited to see what’s next for AMP, and look forward to continuing our work together in the new year. To stay in the loop about all of AMP’s projects and see our 2020 vision, sign up for our newsletter. 

Posted by Alex Durán, AMP Project Marketing at Google

Our 2019 Contributions to Web Platform Interoperability

Websites

Editor’s Note: the following post was written by Frédéric Wang, Partner at Igalia

For the third year in a row, the AMP Project has worked with Igalia to address bug reports and enhancement requests from the Web community. Typical tasks include bug triaging, debugging and analysis, writing tests, landing patches, and discussions with the different actors of the Web platform. This has not only been instrumental to significantly speed up the goals of the AMP project, but has also been generally beneficial to all the developers and users of the Web platform.

This blog post describes our activities for this year, focusing on WebKit/iOS bugs and features. However, our Web platform interoperability effort occasionally extends to other browser communities and standardization groups as well.

Resize Observer

Intersection Observer shipped in iOS 12.2 thanks to Google Engineer Ali Juma. This is a new JavaScript API to asynchronously watch changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.

Resize Observer is a similar API  which we implemented earlier this year under a preference flag. This JavaScript API allows Web developers to asynchronously observe when an element is resized, regardless of why. It is available in iOS 13 beta and the corresponding preference flag has been turned on by default in trunk, so we expect this feature to be available in the iOS 13 release. (Update 01/28/2020: ResizeObserver is now part of Safari Technology Preview)

Scrolling

Apple took over the task we started last year to make <iframe> elements scrollable and this behavior is now enabled by default in the latest iOS 13 version. Other bug fixes involving scrollable elements landed for scroll-snap-align, scrollability after resize find-in-page indicator after scroll as well as for various iOS 13 regressions related to scroll flickering and jittering.

Enhancements have been implemented for the ScrollIntoViewOptions parameter of scroll APIs. Support for logical scroll alignment is shipped in iOS 13. We also continued our efforts to support the scroll-behavior IDL parameter and CSS property and we expect to complete this in the next semester. While working on this, we also detected and fixed Chrome bugs related to the scrollIntoView() method, including cases when a scrollbar is present or when the scroller uses a non-default writing mode.

An old browser interoperability issue for users relates to inconsistent values of scrollLeft, scrollTop and similar APIs and one of our important achievements has been to ensure more reliable and standard behavior happens when setting or getting scroll coordinates. We introduced an option to make Chrome use standard values in non-default writing modes and plan to ship it, after ensuring that it won’t cause serious breakage. Similarly, Apple decided to enable our 2018 changes for the viewport scroller on all WebKit ports.

Besides usual bug fixes, we began implementing other interesting scrolling features, including overscroll customization and overscroll-behavior which are powerful APIs for web developers to control what happens when a scroller reaches its boundaries. We expect more progress on this next year.

Resource Loading

Another exciting goal is to give more power to Web authors to control loading behavior. In particular, this allows the ability to control privacy and optimize page layout.

The referrerpolicy attribute has been implemented to specify how much referrer information should be included within the requests associated to an HTML element loading resources. This is only implemented for the <iframe> and <script> elements and is available in iOS 13 under an experimental feature flag. We will continue to talk to Apple to see when this can be enabled by default or implemented for other elements.

The imagesrcset and imagesizes attributes on <link rel=preload> have also been implemented and are available in iOS 13 under an experimental feature flag. These attributes give the possibility to preload a responsive image represented by an <img> element with relevant sizes and srcset attributes and optimize the selection of the appropriate size for the user device.

We also started to submit patches to support the lazyload attribute on the <img> and <iframe> elements. These attributes enable Web authors to indicate whether it is a good idea to lazily load the element content (e.g. if they are not visible in the viewport until the user scrolls to them) or if their content should instead be loaded right away. These hints are very helpful for browsers to optimize loading of resources.

Finally, we made an experimental support for the intrinsic size attribute in WebKit. This proposal is intended to help browsers to determine aspect ratio or size of an image before its content is actually loaded, in order to avoid extra post-load reflow. This proposal has been superseded by a pure CSS-based approach addressing the same use case. Our experiment was useful for discussions among browser vendors and within the CSS WG and we plan to rewrite our patch to instead implement the CSS-based approach in WebKit.

Conclusion

Collaboration between the AMP project and Igalia to advance the state of the Web Platform has been very successful. There are several pending tasks and new ideas to work on so we look forward to continuing with this effort next year!

Creating Delightful User Experiences Using AMP On Adobe Experience Manager

Websites

Editor’s Note: the following guest post was written by Matthias Rohmer, Development Director at Jung von Matt

TL;DR: Jung von Matt helped BMW drive amazing user experiences using AMP on Adobe Experience Manager.

It’s fairly uncommon that you are free to choose which technology to use to implement for a client’s project. Clients that have chosen an enterprise solution usually seek products and services that complement those choices while allowing for future growth.

When BMW was searching for a company to partner with to rebuild the brand’s website www.bmw.com in 2017, they were looking for that complementary solution. As almost all of BMW’s websites were built using Adobe Experience Manager (AEM), they were looking for a team that was able to both deal with such a high-end system as well as use its capabilities to build an easy-to-maintain, high-performance website.

This was a goal BMW had missed with their already-existing sites. Despite sharing the same backend, they were based on a variety of frontend frameworks and libraries. Fairly early in the process, we decided to bring some fresh air to this stack. Therefore we wanted to introduce AMP as the governing frontend technology. That was where our heads began spinning: what would be the best way to integrate AMP with AEM, especially with a CMS that makes so many assumptions about your frontend development?

Problems to solve when integrating AMP with AEM

After some research it occurred to us that there were three main challenges that we would need to solve in order to render valid AMP pages from AEM: 

  • As AMP requires all of a document’s CSS to be inlined in the <head> we would need to find a way other than AEM’s built-in ClientLib functionalities to render our CSS
  • For all of our pages to be AMP valid we would need a mechanism to only render resource hints (<script async custom-element="amp-carousel" src="https://cdn.ampproject.org/v0/amp-carousel-0.2.js"></script>) for AMP components actually used on a page
  • To be able to progressively enhance the website for returning visitors, AEM would need to be able to render AMP and non-AMP pages at the same time

Inline CSS with AMP

AEM has a fairly streamlined approach of handling a page’s styles: the ClientLib mechanism will take care of combining all the CSS needed for a page based on so-called categories. Based on those categories you can then let AEM create <link> tags in your templates. These will point to built stylesheets containing all your styles.

With AEM’s built-in rewriter pipeline we can use those <link> elements to combine those stylesheets into a common <style amp-custom> tag. The following (highly compressed) code-snippet should give you an idea of how such a transformer might look:

StringBuilder styles = new StringBuilder();
boolean writeStyles = false;

public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
	if (localName.equalsIgnoreCase("link")) {
		// If the element currently in queue is a link tag inspect it
		String href = atts.getValue("href");
		String rel = atts.getValue("rel");

		if (rel.equalsIgnoreCase("stylesheet")) {
			String css = "";
			// TODO: Load the stylesheet from the JCR, store it with others loaded
			// so far and append to styles
		}

		return;
	}

	if (localName.equalsIgnoreCase("style")) {
		if (atts.getIndex("amp-custom")) {
			writeStyles = true;
			// TODO: Use this flag to emit all styles gathered in styles
			// in the transformer's characters method
		}

		return;
	}

	contentHandler.startElement(uri, localName, qName, atts);
}

Only add needed resource hints

Another challenge we needed to solve in order to ensure AMP validity was to only add those <script> elements to the page that are actually needed. We did so by introducing a custom node type to our project:

<ampJS
	jcr:primaryType="bmw:ampJSResource"
	bmw:ampCustomElementTag="[amp-video]"/>

Each of our AEM components has a child of that type holding information about what AMP component it relies on (amp-video in the example above). The advantage of using a custom node type here is that we can safely and quickly query those nodes when a page gets rendered to determine the required AMP components. In code this looks similar to the following:

final PageManager pageManager = resource.getResourceResolver().adaptTo(PageManager.class);
final String currentPage = pageManager.getContainingPage(resource).getPath() + "/jcr:content";

final String query = String.format("SELECT * FROM [bmw:ampResourceHint] AS s WHERE ISDESCENDANTNODE(s,'%s')", currentPage);

final Iterator<Resource> result = resource.getResourceResolver().findResources(query, Query.JCR_SQL2);

while (result.hasNext()) {
	Resource queryResource = result.next();
	final String type = queryResource.getParent().getResourceType();
	ValueMap properties = queryResource.adaptTo(ValueMap.class);

	String[] usedComponents = properties.get("bmw:usedAmpComponents", String[].class);
	if (usedComponents != null && usedComponents.length != 0) {
		// TODO: Store all used components somewhere for later rendering
	}
}

This piece of logic can then be easily called in an HTML template by using a data-sly-use attribute in combination with data-sly-repeat to print all required resource hints to the head of the page.

Serve AMP alongside a PWA

For www.bmw.com we wanted to make sure that the site lands on the user’s screen as fast as possible. To achieve this every first-time visitor will receive the AMP version of our page. At the same time, we wanted to implement features that AMP alone could not offer but a PWA (which is still based on AMP!) could.

This means that our application would need to be able to serve two versions of the same document. Luckily for us this functionality is already available with AEM by using Sling selectors.

To establish a selector all you need to do is implement two templates alongside each other. The one the Sling engine should resolve to by default is simply called html.html. The other one is named after your selector. In our case it’s pwa.html which makes all of our articles either accessible in their pure AMP version via brooklyn-beckham-car-photography.html or brooklyn-beckham-car-photography.pwa.html with our PWA features.

Using this method, we had found a way to independently serve our valid AMP pages alongside our PWA. But how would a user ever end up on our Progressive Web App? That was where amp-install-service-worker had its shining debut. By using this AMP component, the www.bmw.com is installing a Service Worker as soon as the user visits one of its pages in any of the AMP caches. From that point on we were then able to rewrite all requests going to brooklyn-beckham-car-photography.html to brooklyn-beckham-car-photography.pwa.html instead in order to silently enhance the user’s experience.

For us, those three were the main challenges when we were building BMW’s new international marketing website. In the end, all of them were solvable without reinventing the wheel, by using functionalities that are already built into AEM and AMP in creative ways.

AMP and Adobe have teamed up with Bounteous to make it even easier to build AMP sites on Adobe Experience Manager. Learn more and get started here.

Written by Matthias Rohmer, Development Director at Jung von Matt

People Behind The Code: NDTV’s Rise To Global Heavyweight

Websites

New Delhi Television Limited (NDTV) is one the most-watched news networks in India. They were also pioneers in bringing their content online with an accessible, user-friendly design.  

As smartphones became more affordable and accessible, mobile usage exploded across India in 2015. However, mobile data speeds also varied wildly from region to region. NDTVs development team quickly honed in on fast loading times as a way to gain a competitive edge, so they became early adopters of AMP. 

Vikas Kumar, NDTV

We caught up with Vikas Kumar, the head of NDTVs technology team, to talk about the impact AMP had on their mobile-first approach. 

Could you give us a bit of background on your company, and what made you decide to use AMP? 

NDTV launched in 1988 as a production house for Doordashan, a state-run news channel. We eventually launched our own independent channel, with dedicated content across news, sports, entertainment, finance, tech, to name just a few. 

We launched NDTV.com in 1999, and in the last two decades have grown into one of the top 20 news websites in the world. Through a commitment to quality content, UX, and localisation (our content is available in English, Hindi, Tamil, and Bengali), we now average close to 200 million monthly active users. 

We always saw page loading time as key to growing our online audience. One of our team members read an article about AMP, which got the developers talking. Our Google Account Manager sent us an email about it the day after, so it seemed like fate was calling!

Overall, we felt that AMP’s faster load times, and optimizations like pre-caching on SERP, could help us deliver a superior user experience.

Did integrating AMP present any challenges?

No, not really. Our SEO had been moving away from desktop towards mobile for some time, and with mobile index first taking precedence over desktop search, we knew it was the right option. 

We had some initial doubts about integrating third-party widgets, and whether or not we could maintain the existing look and feel of the site, but we found easy workarounds for everything. For example, <amp-iframe> is very useful for rendering widgets or elements that use custom JavaScript.

How did AMP impact your business? 

For ad-supported sites like ours, more impressions means more revenue. The faster loading pages and better user experience helped us establish a regular audience and generate income that we could reinvest into site expansion.

In this industry – even a one second difference has a massive impact in reducing bounce rates and increasing average session duration. After implementing AMP, average First Meaningful Paint (FMP) went from three seconds to one. 

AMP also prevents render blocking JavaScript by making it Asynchronous, and it eliminates third-party JavaScript which drags on loading time. It made us fast and agile. 

Our competitive advantage has decreased over time, as most publishers in India have embraced AMP and also take advantage of the platform. However, AMP really helped establish the channel with our target audience from the get-go. 

What advice would you give to anyone considering AMP? 

Make sure you familiarize yourself with all of the available AMP components; it will help you build a strong initial site framework. 

You should also continuously assess whether or not your AMP pages are valid, and if they’re not, take the time to understand why. The AMP website has resources for everything you could hope for, so we visit all the time.  

Did you find any AMP components difficult to implement? Are there any components you’d like to try out? 

We found <amp-live-list> a bit tricky to begin with! We wanted to run graphics of real-time election results on our site, but the third party JavaScript in it caused a problem.

In the end, we just posted our problems to the community and it was great that we could find a solution with the help of fellow developers, because AMP is open sourced, it’s constantly evolving so it’s great to collaborate and help each other.

We’re really looking forward to testing <amp-web-push>, which just launched recently.

What excites you most about the future of AMP? 

News like AMP being part of the OpenJS foundation gives us confidence that it’s being built with publishers and developers in mind, and is an open platform. New components and experiences like AMP Stories just add to the excitement. AMP+ PWA with one line of code would be awesome to have!

Vikas and his team are excelling with AMP – and you could too. Check out the resources on amp.dev for everything you need to further your career with AMP. Grow your AMP skills.

Have you been using AMP as part of your strategy? Tell us your story! 

To stay up-to-date with the latest news, features, and tips about using AMP, sign up for our newsletter.

Posted by Alex Durán, AMP Project Marketing at Google

Skimlinks’ Pioneering AMP Extension Helps Maximize Commerce Content Revenues

Websites

Editor’s Note: the following article was originally posted on Skimlinks.com by Debbie Gainsford, Senior Product Marketing Manager at Skimlinks.

It’s tough to be a publisher in 2019. With advertising revenues falling, anything that can effectively and consistently monetize your content is a must, so it’s no surprise that uptake of Skimlinks’ innovative AMP extension has exploded since launch late last year.

The solution, which is the first of its kind, extends our core commerce content technology to AMP articles, enabling publishers to seamlessly monetize affiliate links to merchants.

To date, more than 100 publishers have installed the extension on over 500 domains, accounting for 10M+ affiliated clicks. Some of our publishers have seen an uplift of more than 400% in both sales volume and order value, compared to the same article on the desktop or standard mobile version.

Why is AMP so Important?

As readers and consumers become easier to reach on mobile devices, it has become critically important to give them a seamless experience when they are searching for and reading mobile content. For publishers, AMP has been an invaluable development, as it helps mobile pages load faster, providing an improved reading experience for users. Since its launch in 2015, AMP has grown to power billions of pages across the web, from tens of millions of domains.

The priority for publishers on the AMP platform is to ensure that they can make a profit from their mobile content. In a survey of 124 publisher executives by Digiday this May, 51 percent of publishers using AMP said it was “very significant” or “extremely significant” as a driver of revenues, compared to only 28 percent for Facebook and 25 percent for YouTube.

Our CEO, Sebastien Blanc says “ With the rapid growth of mobile for content and for commerce traffic, mobile monetization has been a top priority for almost every publisher we work with. With this solution we’re confident we can give commerce content a place on smartphones, where more and more readers engage with editorial publishers’ content.”

How leading publishers are monetizing their Commerce Content on AMP

Leading Publishers have installed the Skimlinks AMP script to meet several different business challenges and objectives.

New York Media: Extending to new merchants for uplifts of more than 140%

“We did monetize some AMP articles that linked to Amazon, but we were not monetizing our non-Amazon content, so that is why we installed the Skimlinks extension. Since then we’ve seen more revenue and increased search volume. We now publish all of our content on AMP.” Camilla Cho, GM eCommerce

Around 30% of The Strategist’s audience consume articles via AMP, and they’ve seen great success when monetizing these articles. Their top performing AMP article generated 146% more revenue than the desktop version.

VerticalScope: Monetizing more than just display ads on AMP articles

Kendal Clarke, General Manager of Commerce says “Before using Skimlinks we were just monetizing the AMP traffic with basic display ads, and we didn’t have any commerce links. We installed the Skimlinks AMP extension to capitalize on the revenue we were missing. We knew we generated commerce revenue on these pages via other channels and so it seemed like a no brainer to work with a partner who could help us access this incremental revenue.”

Group Nine Media: Creating a brand new revenue stream with Commerce Content

Group Nine Media have only recently begun their commerce content journey to build a new revenue stream for their business. Working with Skimlinks they have been able to monetize their content across all channels, including AMP. And AMP is a key channel; so far this year, 42% of the Thrilist traffic is via AMP and a staggering 61% of their search traffic is via AMP.

Sam Clanon, Director of Commerce Strategy says “We installed the Skimlinks extension to bring affiliate links to AMP articles for the first time at scale. Since installing the extension, we have captured affiliate commerce revenue using links that are already present in our content. If AMP comprises any meaningful portion of your traffic, Skimlinks is one of the quickest and easiest ways to start generating affiliate revenue from that content.”

Immediate Media: Saving time and resources by automating a manual process

“On radiotimes.com, 15-20 percent of all content is via AMP. AMP traffic is hugely important but monetizing it has always been a challenge. Before installing the Skimlinks AMP extension, we were manually creating and adding affiliate links using the Skimlinks link wrapper. It’s really streamlined the process – no more time spent creating manual links.” says Glenn Caldecott, Affiliate Manager

“If you’re looking to monetise mobile traffic through affiliate marketing, the Skimlinks AMP solution is a no-brainer. The integration is lightweight and easy to implement.”

Reach: Measuring the performance of AMP articles

Publishers know how important analytics are in developing the right content, for the right audience, to maximize revenues. You need detailed insights to understand the performance of your commerce content strategy and to continually improve this key revenue stream, which is why Reach chose to install the Skimlinks AMP script.

“Before installing the Skimlinks AMP extension, we had built a server side solution that adds the affiliate links on the backend, so they’re included on AMP pages. However, we weren’t able to track the impressions and clicks data. Since installing the Skimlinks AMP extension, we can now track impressions as well as clicks which means we can measure CTR on AMP pages, something we couldn’t do previously with our server side solution. We’ve seen a 9% increase in order value from AMP for a top article.” Jeremy Mundy, Senior Product Manager, Revenue

Advantages of Mobile for Commerce Content

Publishers sometimes believe that commerce content on mobile is only appropriate for low involvement, low cost items. However, for many consumers, purchasing on mobile has become second nature, and they are increasingly confident to buy higher ticket items, and those that involve more complex purchase decisions.

This is where affiliate links in mobile content offer consumers a valuable service, as they can go straight from advisory articles to purchase the items they are reading about. We’ve seen from our data that consumers are purchasing TVs worth over US$5,000 from commerce content.

Another area where mobile has an advantage for commerce is in personal items; mobile is a much more private medium than desktop, and when customers are researching intimate or sensitive purchases, they are likely to be more comfortable doing so on a smartphone than on a desktop.

A high-profile example of this comes from New York Media, who were winners of the recent 2019 Commerce Award for Publishers (The CAPS) for Best Mobile Article. The winning article, from The Strategist, was their roundup of the best vibrators available on the market. This was part of a series entitled Things We Don’t Talk About, which focused on finding and recommending the best hygiene, sex, and bodily function-related things we all need but might be too embarrassed to ask about.

Ready to monetize your AMP content?

Like the Skimlinks JavaScript, the Skimlinks AMP integration is a couple of lines of code that needs to be applied to AMP pages. You only need to install the code once, and then there is nothing else for you to do; links will automatically be affiliated and you will start generating revenue from your AMP articles. After installation, no maintenance or updates are needed.

“If you have AMP traffic that you are not currently monetizing then this is a simple and effective solution. One quick implementation, and then you don’t have to worry about it. The extension has given us an easy passive revenue stream, as we have been able to monetize all the different advertiser partners that we work with” Kendal Clarke, General Manager of Commerce, VerticalScope

To quote the winner of the best Mobile Article at The CAPS, “I cannot imagine why someone would not use the Skimlinks AMP extension for publishing their content.”

Skimlinks Publishers can access the AMP script in the Publisher Hub.

For Publishers not yet working with Skimlinks, sign up now.