AMP Camp: Using templates in the client and the server



Welcome to the latest in our series of posts about AMP Camp, our demo that shows how to create an interactive site with AMP! In this series, we’ll discuss the techniques and tools we used in creating it, as well as best practices we developed. If you’re interested in creating an interactive site with AMP, we hope you’ll learn something!

In the previous post, we talked about the build process used to create our site. In this post, we’ll discuss how to use templates on both the client and your server.

AMP is commonly used to serve pages instantly through link sharing platforms, what’s referred to as Paired AMP. In a previous post, we’ve discussed the benefits of using AMP as the primary framework to build your entire website, an approach that’s called AMP-First.

In this post, we’ll discuss how to produce dynamic content in AMP-First sites. That way users always access fresh data, whether they arrive at the pages from the AMP Cache, or from the site’s origin.

To achieve this, we’ll explore how to differentiate client-side rendered parts of the page (not mediated by the cache) from server-side rendered ones (that can be safely cached).

Also, to simplify our development we’ll see how we can use templates in the client and the server by using the same templating engine. This can make our code more readable and maintainable, as long as we make sure to differentiate clearly which parts have to be rendered by which layer.

As a recap, let’s review how content can be accessed in AMP-First sites.

Dynamic content in AMP-First sites

In AMP-First sites, users can access your pages on two different origins:

  • Site’s origin: These AMP pages are hosted by the site’s owner. Users commonly land on those pages by navigating the site’s URLs.
  • AMP Cache origin: These versions of the AMP pages are stored after being discovered by search engines like Google and Bing (for example at or Users commonly land on those pages after clicking on links from search results or link sharing platforms.

In the first case, site owners have full control on the caching strategies to apply, for example, by using traditional HTTP Caching strategies and/or service workers.

In the case of pages served from an AMP cache, even when the site’s owner can force updates (e.g. by using update cache API), by default the cache will follow a stale-while-revalidate model. This consists of checking the origin’s caching headers (such as max-age and s-max-age) on each user visit, and requesting a new copy to be fetched in the background, if the resource is stale. For that reason, one user might see stale data until the latest version of a page has been fetched.

For many sites, relying on the default behavior of the AMP Cache is acceptable. For example, frequently accessed articles on news sites are kept fresh automatically, and having, at most, one user see stale content from time to time might not be a big issue. 

E-commerce sites usually have stricter UX requirements: showing stale prices even to a single user could prevent an entire purchase, or what is worse, negatively impact the user’s trust on the brand.

In high impact cases, you can apply a mixed strategy for dynamic data, to make sure that critical fields are always fresh while leveraging the speed benefits of the cache for non-critical parts of the page:

  • Client-side rendered data: For critical fields (like prices) client-side requests can be used. These requests are not mediated by the AMP Cache, and can be implemented by using dynamic AMP components like <amp-list>, <amp-bind> and <amp-access>.
  • Server-side rendered data: Non-critical parts of the page that change less frequently (like a product title) can be fully rendered in the server. This is commonly achieved by combining dynamic data (from databases, APIs, etc), with static HTML templates.

As a result, when the page is being served, the resulting HTML code will contain fields already populated (non-critical), along with calls to components like <amp-list> for client-side generated ones (critical fields). 

In the following snippet, the parts marked in blue are server-side rendered fields, while the red ones are fields that will by resolved later in the client:

<div class="product-details"> <h1>Stretched Jeans</h1> <amp-list height="24" layout="fixed-height" src="/static/samples/json/product.json"> <template type="amp-mustache"> Price: ${{price}} </template> </amp-list> <p class=”product-description”>A classic choice for every day.</p> </div>

The code looks simple, but it’s the result of some complex work done in the backend. Next, we’ll explore how to achieve that by using popular web technologies.

Implementing a server-side rendering strategy

To put to practice the strategy discussed before, we’ll use a popular backend technology: Node.js.

As mentioned, for the server-side rendered parts of pages, you need some way of combining data fetched from APIs and databases with static HTML templates. In a Node.js environment, this is usually accomplished via JS templating engines.

There are many available options on the market. In this post, we’ll explore a popular one: mustache.js. Besides the fact that it’s easy to implement and widely used, one of the advantages of picking this templating engine is that it’s already used by AMP to render the responses of dynamic components, like <amp-list>, through a component called <amp-mustache>. This saves us the effort of learning another technology while keeping our code more coherent and readable.

A typical mustache template contains any number of mustache tags. By default these tags are written with curly brackets (e.g. {{price}} and {{availability}}).

Even when they are simple to write, these templates are “logic-less”, meaning you can use things like conditions in the templates, but not much more. Most of the logic will be executed and contained in data objects that are sent to these templates to populate fields.

The challenge of using the same templating engine for client and server is that we’ll be using the same tags to populate both client and server-side rendered fields.This can lead to collisions.

In the previous example code, if we were using the same default mustache tags {{ }} in the client and server, when the engine runs in the server, and finds the following code:

<div class="product-details"> <h1>${{product-title}}</h1> <amp-list height="24" layout="fixed-height" src="/static/samples/json/product.json"> <template type="amp-mustache"> Price: ${{price}} </template> </amp-list> <p class=”product-description”>${{product-description}}</p> </div>

It will replace each dynamic field with the value of the corresponding property in the object that is used to populate the template. The resulting version of the page will be:

<div class="product-details"> <h1>Stretched Jeans</h1> <amp-list height="24" layout="fixed-height" src="/static/samples/json/product.json"> <template type="amp-mustache"> Price: $50 </template> </amp-list> <p class=”product-description”>A classic choice for every day.</p> </div>

When the AMP page is served, the following sequence will take place: 

  1. <amp-list> will be executed at page load time.
  2. The response will be passed to <amp-mustache>.
  3. Since the price has already populated in the backend, mustache won’t find any dynamic fields to resolve.

This prevents our goal of making sure that the price is always fresh.

To avoid this, you can use custom delimiters, to declare a different set of tags in the client and the server. For example:

  • {{ }}: For fields rendered by <amp-mustache> with the result from <amp-list>.
  • <% %>: For fields populated by mustache in the server.

The resulting code will look like the following:

<div class="product-details"> <h1><%product-title%></h1> <amp-list height="24" layout="fixed-height" src="/static/samples/json/product.json"> <template type="amp-mustache"> Price: ${{price}} </template> </amp-list> <p class=”product-description”><%product-description%></p> </div>

By combining these delimiters in a template, the server will first populate all the fields marked with <% %>, and leave the ones marked with {{ }} untouched, so that they can be used by <amp-mustache> after <amp-list>  executes.

Going the extra mile: serving different versions of the page according to user agent

In the case of AMP-First sites, one could apply an additional optimization, by serving two different versions of the page to users and crawlers:

  • For crawlers – Mixed client and server-side rendered approach: The strategy discussed before can be applied only for requests coming from the Google crawler, by verifying Googlebot using a reverse IP lookup. When the crawler fetches the AMP URLs, those are the versions that are going to be retrieved, stored and served from the AMP Caches.
  • For site’s origin users – Fully server-side rendered approach: If the request doesn’t come from a bot, the page can be fully server-side rendered, so that user’s navigating the site from the origin don’t need to incur into any additional latency from <amp-list> requests unnecessarily.

That way, since users navigating on the site’s origin won’t run into the risk of seeing potentially stale content, there’s no need to make any client-side request for critical fields. In those cases, all the content can be fully server-side rendered, to avoid incurring potential latency of client-side requests.

Breaking up the serving strategy in this way ensures the best possible performance for users accessing the site from different surfaces.


In this post, we have explored a strategy to make sure that the AMP content served is always fresh by combining client and server-side rendered content in the same page. 

To that end, we explored how to use templates in the client and the server, by using popular web technologies: Node.js and mustache.js.

For concrete examples of application of this pattern, you can take a look at the code of AMP Camp

The product details page is a good example of applying this technique. It contains a mix of fields, written with different tags: {{ }}, for client-side requests, and <% %> for server-side rendered parts of the page, as discussed throughout this guide.

If you want to take this implementation further, you can analyze the user-agent of the request, and only serve mixed client and server-side rendered pages to search engines, while serving fully server-side rendered versions of those pages to users navigating on your origin.

Written by Demián Renzulli, Web Ecosystem Consultant, Google

Infinite recommendations with a new and improved amp-next-page


A key part of AMP’s mission is ensuring the “long-term success of every web publisher”.  To further that mission, we know just how important it is to help web publishers increase the reach of their content. Previously, we released <amp-next-page> as an experiment to help publishers turn an AMP page into a continuous scrolling experience by loading additional recommended content from the publisher’s website when the user reaches  the end of the document. This component was intended to help publishers increase engagement on AMP pages by increasing the time users spend on site.

We’re excited to announce <amp-next-page>, version 1.0! The release of version 1.0 comes with all the benefits of publisher testing and feedback from version 0.1. Read on to discover new features and learn how to migrate.

Forbes using <amp-next-page> version 1.0 (Link)

Why upgrade to version 1.0?

Support for infinite recommended pages

With <amp-next-page> version 1.0 you can load an infinite number of pages as opposed to version 0.1 which only allowed publishers to specify 3 recommended pages.

Analytics Support

The <amp-next-page> component supports analytics on the hosted page as well as on subsequently loaded pages as long as you are using <amp-analytics> and <amp-pixel> on those pages. This also means you can use any analytics provider that is supported by <amp-analytics>.

Two custom analytics events are also provided on the host page to indicate transitioning between pages. For more information on analytics support go here.

Customize separators to increase monetization 

<amp-next-page> renders a separator between each page to help the user delineate between two pages. While this separator does have a default style, publishers can also style this separator to reflect their brand. Publishers can go one step further to place <amp-ad> in this separator, to increase the potential for monetization as the user consumes more content on site.


Like other AMP components, <amp-next-page> is accessible by default. The default recommendation box and separator both have a generic, non-localized aria-label describing their content and are keyboard-focusable. The aria-label can also be overridden to accommodate localization.

Instructions on migrating to version 1.0

For instructions on how to migrate from version 0.1 to version 1.0 please visit the migration section in the documentation.

Our thanks go out to you, the AMP development community, for your feedback. As always, please let us know if you have any issues or feature requests.

Posted by Naina Raisinghani, Product Manager, AMP Project

Creating accessible sites with AMP


The open web is powerful due to its ability to give everyone access to the same information. This means the Web needs to be designed to work for all people regardless of their location, hardware preferences, language, or abilities. AMP invests in this work through the UI and Accessibility Working Group with the goal to ensure that all AMP experiences are WCAG AA compliant by default. This means that AMP doesn’t just make it easy for you to create accessible sites, it goes one step further by making sure creating an inaccessible experience is not the default outcome.

This blog highlights some of the work that the AMP Project has put into creating accessible components and some key principles and tools that developers should keep top of mind as they create web experiences.

Enabling accessible sites

Accessibility is an important early consideration when designing AMP components. Our Design Reviews and Intent to Implement process focus on making sure that the components we release do not exclude people with varying accessibility needs. This results in AMP enabling you to create pages that meet the W3C WCAG 2.0, Level A and AA guidelines which are an important part of laws with accessibility considerations internationally. 

However, like any other framework, while AMP does allow you to create accessible experiences  and offers nudges to do so, we can’t enforce that all websites using AMP are meeting WCAG guidance. For example, while <amp-img> allows you to specify an alternate text, we don’t enforce that all images have alternate text; even if we did, we couldn’t enforce that the string provided was actually a description of the image without running extensive Machine Learning models.

How we reduce developer effort

Below is just some of the heavy lifting that the team has done to make the commonly used AMP components and actions accessible by default. 

  • <amp-sidebar>: Focus management was top of mind for the team as it implemented <amp-sidebar>. This is why <amp-sidebar> moves focus to the sidebar when opened and moves focus to the opener automatically when closed. It also provides a focusable “close” modal for users interacting with the component via a screen reader.
  • <amp-carousel>: For <amp-carousel> version 0.2, the next and previous buttons always announce the current slide and the total number of slides when being accessed by a screen reader. 
  • <amp-list>: By default, <amp-list> adds a list ARIA role to the list element and a listitem role to item elements rendered via the template. If the list element or any of its children are not focusable, or “tabbable” (accessible by keyboard keys such as the a and button elements or any elements with a positive tabindex), a tabindex of 0 will be added by default to the list item. This makes them explicitly accessible via screen reader. 
  • <amp-autocomplete>: The <amp-autocomplete> component will annotate its generated suggestions to announce that the input field will display suggestions and fully supports navigating through them and selecting them via keyboard. When suggestions are available, the user can use `Up` and `Down` arrow key navigation to visually highlight completed inputs, or for screen readers, to read them aloud.

What we intend to do in the future

Since accessibility is a top priority for the UI and Accessibility Working Group we plan to continuously invest in improving AMP’s default accessibility in the future. Below are a few themes we intend to work on:

  • Improve our contribution documentation by adding guidance on accessibility testing that needs to be done by anyone contributing components.
  • Improve AMP component documentation to clearly document the accessibility features the component gets right and what added work the developers need to do themselves (such as captioning all videos on the page).

Accessibility resources that the AMP team finds useful

To ensure that your sites are meeting WCAG AA guidelines, we suggest using the following tools to automate your testing. 

  • axe-core: An accessibility testing engine for websites that integrates with any existing test environment so you can automate accessibility testing alongside regular functional testing.
  • WAVE: Tools for facilitating web accessibility evaluation by providing a visual representation of accessibility issues within the page. 
  • Lighthouse CLI: Chrome Lighthouse builds on axe-core to publish a report on how accessible a site is. 
  • Other Web Accessibility Evaluation Tools suggested by the W3C here.

However, automated tools can’t always catch all issues, and the best recourse is to have a developer with understanding of the WCAG guidelines review your markup and CSS to ensure that best practices are being utilized.

As always, please let us know if you have any issues or feature requests especially in the accessibility space.

Posted by Naina Raisinghani, Product Manager, AMP Project

Introducing the fastest and most user-friendly content encryption


Stuck between keeping content secure and providing a great user experience? The debate is over! We’re introducing a new type of premium experience! Client-side content encryption is a fast and user-friendly solution to protect and serve premium content. All while providing the same level of security as server-side validation!

What’s the problem?

A server-side paywall process may look something like this:

The user lands on a page for subscribers only. 

The page asks the user for the login credentials then sends them to the server.

The user waits…

The server returns the premium content to a subscriber, 

or a paywall for non-subscribers, by refreshing the current page. 

AMP is all about speed, portability, and user happiness. However, server-side validation can cause a virtual detour. Users at the edge of their seat are dying to dive into breaking news and making this round trip can cost double the data for the same amount of content! Not to mention, users will drop off if they have to wait too long or if the process becomes too tedious.

Publishers often take a chance and aim for a more user-friendly experience by hiding content with CSS. But this comes as a cost! Savvier non-subscribers can break through these defences and access content, for free. 

So, how do we get the best of both worlds?

Get the solution!

The folks at AMP and Google created a new protocol to streamline the user’s experience and secure the publisher’s content. 

Client-side content encryption serves protected premium content before authenticating the user. This protocol uses a combination of asymmetric and symmetric-key cryptography to serve encrypted content. After verifying the user, it’s decrypted all on the client-side. Additionally, AMP handles all the heavy-lifting of encrypting and decrypting. All content is easily indexed by Google and ready to serve from its AMP cache, all while providing the same level of security!  

This is the most user-friendly premium content protection that doesn’t compromise security or speed. Learn how to implement this solution by reading Protect your subscription content with client-side encryption.

Written by Crystal Lambert and Elijah Soria
Images by Chen Shay

Cumulative Layout Shift (CLS) in AMP


Content jumps in web pages can be very detrimental for the user experience. As a result of them users might end up clicking the wrong elements or losing their reading position – thereby causing lower engagement rates, higher bounce rates and ultimately lost business.

In this post we’ll analyze how AMP mitigates these content shifts, allowing us to create user-friendly experiences on the web with little effort, thanks to its internal layout system.

We’ll also review different ways you can measure shifts in AMP and non-AMP pages, by using different tools.


Many times when visiting web pages, users notice that the page content jumps.

News sites, for example, tend to suffer from layout shifts: it’s not uncommon to be reading an article and noticing that the text moves down to make space for some resource like a video, an image or an ad.

AMP however was designed to be different, and AMP components and design principles make it near impossible to trigger layout shifts. This happens thanks to the AMPHTML Layout System, which lets AMP size the page elements before any remote resources, like images, are completed. As a result, rendering and scrolling jank is reduced, which contributes to a much better user experience.

Up until recently, it was hard to measure the exact impact of layout shifts in sites. Now thanks to a new metric: Cumulative Layout Shift (CLS), we can have a more exact idea of how meaningful this is.

The Analysis

The CLS score is based on the layout instability API, which detects when an element that is visible in the viewport changes its start position between two frames. A single shift is bound between 0-1, where 1 means that all of the visible content has shifted. The overall CLS score is the cumulated value of all layout shifts happening on page load.

To see how AMP performs with respect to CLS in the wild, we used a puppeteer based script to analyze a random set of 560 URLs from 450 different domains. The pages selected are largely from different domains, spanning different categories and have a paired AMP version, which we checked for validity first. As AMP is historically mostly implemented for mobile, we restricted the analysis to mobile viewports and connections, but be aware that CLS as a metric is obviously also very important for desktop, and should be measured and tracked there as well. Within our test set we found that CLS in AMP frequently outperformed the non-AMP version.

In 71% of cases the AMP version has a smaller CLS score than the canonical, and in fact in 86% of cases CLS for AMP is zero, as expected due to its design paradigms of keeping layout stable. 

There are use cases where layout-shifts are impossible to avoid, for instance when user-consent is needed and AMP team is currently investigating to minimize the effect on CLS even more. 

In the following section, we’ll analyze different ways to calculate CLS in AMP, including the method we used to arrive at the previous conclusions.

Calculating Layout Shifts in AMP Pages

In HTML pages you can measure CLS by executing a snippet of JS code. In AMP you can’t run custom JS code outside AMP components, or <amp-script>, but here are some easy ways of obtaining this score through different tools:

1. CLS Calculator tool:

The CLS Calculator tool lets you input a URL and easily obtain its CLS score. 

Here are some steps of how to run this, by using a site completely built on AMP:

1. Go to

2. Enter a URL. In this case,

3. Run “Get CLS”.

The test returns the CLS value associated with the page:

In this case, the value of CLS is zero, which means that elements in the page haven’t shifted. As said before, this is one of the benefits of AMP, which leads to a better user experience.

The tool has another great feature: if you have a site that uses AMP in “Paired Mode”, meaning that you are publishing AMP and non-AMP versions of your pages, you can enter the canonical URL and will return a comparison between AMP and non-AMP pages.

Feel free to use this tool on your own site.

2. Lighthouse:

Lighthouse, another popular web performance tool, has recently incorporated CLS as a metric (currently in beta).

Here are the steps to obtain a report for an AMP-First site:

1. Open the command line tool.

2. Run: npm install -g lighthouse@next to install the cli tool.

3. Run: lighthouse to generate a report.

This will generate an HTML file in the current directory. 

Opening the file in the browser shows the CLS value of the site (in the case of, zero):

3. Webpagetest:

Webpagetest is one of the most popular web performance tools. It lets you obtain the main performance metrics for a website, simulating different devices and locations.

The tool currently provides CLS as a metric, inside one of the internal options. This could change in the future, making the metric available in the main results page.

To obtain the CLS for

1. Go to

2. Enter as URL.

3. Click “Start Test”.

4. Once the results are received, click on “View JSON result” in the top left corner:

This will return a JSON file with the results of the test.

Searching for the first occurrence of “CumulativeLayoutShift”, will show the value of this metric, which is zero as in the previous tests.

4. Puppeteer:

Puppeteer can also be a great option for automatic retrieval of CLS for a given website, or to even batch process many websites. Integration through puppeteer makes also sense for testing and QA purposes. 

A (truncated) example snippet on how to get CLS with Puppeteer can look like this (full code here):

const page = await browser.newPage(); const client = await; // throttle network and CPU as needed, see e.g. here: // await client.send('Network.enable'); await client.send('ServiceWorker.enable'); await client.send('Network.emulateNetworkConditions', Good3G); await client.send('Emulation.setCPUThrottlingRate', { rate: 4 }); await page.emulate(phone); // inject a function with the cls measurement code code from : // await page.evaluateOnNewDocument(calcJank); await page.goto(url, { waitUntil: 'load', timeout: 60000}); await page.evaluate(calcJank); let cls = await page.evaluate( () => window.cumulativeLayoutShiftScore );


Visual stability is a very important aspect of the user experience. AMP’s architecture is intentionally designed to avoid layout shifts and make it easier to create user-friendly experiences on the web.

In this post we have explored different ways of calculating Cumulative Layout Shift for AMP pages. 

  • For sites that use AMP as their primary framework, evaluating this metric can help them understand better the benefits that AMP can bring to users.
  • For those using the “Paired AMP” approach, comparing  the value of CLS on their AMP vs. non-AMP versions of the pages, can help them understand which performs better and guide future technical decisions.
  • For those that are not yet using AMP, running the previous tests in AMP pages from popular sites (for example, news publishers) can give a better understanding of the benefits AMP could potentially bring to their own sites.

Written by Demián Renzulli, Martin Schierle, and Gilberto Cocchi, Web Ecosystem Consultants, Google

Adobe Experience Cloud Extends AMP Support


Adobe has been working to seamlessly integrate AMP support into Adobe Experience Cloud applications for its customers, from publishers to advertisers; below is a summary of some recent integrations across Adobe Experience Manager, Adobe Campaign, and Adobe Analytics.

Adobe Experience Manager (AEM)

To create new ways for their customers to deliver seamless, user-first mobile experiences, Adobe recently announced AMP support for Adobe Experience Manager core components. AMP-enabled sites built with Adobe Experience Manager can deliver more efficient, discoverable, and optimized experiences for mobile users – check out the PR on Github.

With the new compatibility, developers are able to create a single set of code to deliver both HTML and mobile optimized instances without the need to write separate content or code. Additionally, because AMP’s open source code is available to the Adobe Experience Manager community, developers can build custom components to meet the needs of their individual sites.

By enabling Adobe Experience Manager components to output AMP, the two open source initiatives came together to strengthen the mobile content ecosystem and create unmatched mobile experiences. To learn more about the great work by Adobe Experience Manager partner, Bounteous to make this happen, check out this article. 

Adobe Campaign

As another demonstration of its commitment to innovation, Adobe announced AMP for Email compatibility with Adobe Campaign Classic, part of Adobe Experience Cloud. This functionality allows the use of AMP components inside dynamic, engaging emails sent from Adobe Campaign Classic. 

Compatibility with AMP for Email allows marketers to innovate and engage with users directly within emails. With the new functionality, customers can leave feedback, make reservations, take polls, and more without ever leaving an email.    

With consumers spending so much time in their inbox, Adobe gives its customers a way to stand out among the over 290 billion emails that are sent every day. For more information about AMP for Email compatibility within Adobe Campaign Classic, check out the Adobe blog.  

Adobe Analytics 

Adobe Analytics brings a seamless integration to AMP pages by integrating AMP linker into Adobe Analytics. AMP Linker is a new capability in AMP that helps keep user sessions in sync by decorating outgoing links from AMP cache with params such as AMP Client ID in a URL parameter. Adobe Analytics consumes the parameter and writes it down as a first-party cookie.

By integrating AMP linker and Adobe Analytics, organizations will be able to keep user sessions in sync between AMP and Non-AMP pages. Learn more about Adobe Analytics’ AMP support here

For more information and to learn the latest from Adobe, check out Adobe’s first-ever digital Adobe Summit that took place March 31st, 2020.

Posted by Jeffrey Jose, Product Manager, The AMP Project, Google

People behind the code: The Axios ascent

Digital media company Axios has stepped onto the scene with a quick, smart, audience-first experience. Sound familiar?

Fast, audience-first and efficient – talk about mission alignment. Marissa Halpert, a front end engineer working out of Washington, D.C., and Rick Terrill, leading the front end engineers out of Texas, couldn’t ignore the connection! Together, they led Axios’ shift to AMP-first. We invited the duo for a chat to discover how a beta test in 2019 escalated into a full migration.

Marissa and Rick

Thanks for joining us! To kick us off – could you share a bit about your background? 

Rick: I got into tech through a love of video games, and dreamed of becoming a game developer. That quickly turned into a love of Flash (which, I assure you, was super cool back then). A front end nerd was born. 

Marissa: I was introduced to programming in high school before going on to study computer science at James Madison University. Since then, I have done full-stack development at a Fortune 500 company and led front-end web projects at a digital agency, before joining Axios in April of 2019. 

Can you tell us a bit about Axios, and what attracted you to AMP? 

Marissa: Axios launched in 2017 with a mission to make people smarter, faster. We share breaking news across politics, tech, science, media, business, and the world.

Rick: At Axios, we’re all about being audience-first. We want our site to be fast and easy to use, with content that’s worthy of the reader’s time. We figured going AMP-first was the best choice to help us achieve that, and it’s really worked out.

How did you approach making the switch to an AMP-first website?

Rick: It was a two-phase approach. We created a beta in 2019, using an ADR to work through the challenges and agree on details. We implemented our single-story page as a separate application, which was responsible for rendering two or three years of content and a few ad units. 

Marissa: Rick’s team wrote most of the beta code before I joined Axios. Wanting to understand more about AMP I attended the Roadshow in Atlanta and began advocating for going AMP-first. After getting the thumbs-up, we kicked off a full discovery process. We chose Next.js 9.1 because of its built-in AMP integration and our team’s existing React knowledge. 

What drove that decision?

Rick: The beta test was something quick and easy that we could observe  – but our dev team had to maintain two separate code bases ( and They were both React.js, but they were also completely different ways of thinking. We wanted to make sure everyone was working in the same way together, using React.js and Next.js. 

Marissa: AMP had come a long way since our AMP beta debuted. Ultimately, the lightning-fast speed that AMP is known for (along with the vast component library and community outreach) convinced us to go AMP-first. 

Were there any challenges you had to overcome?  

Rick: All of our ads at Axios are native ads that we build in-house, and our existing creatives varied in size and dimensions. So the biggest challenge by far was adjusting to the fixed-height world of AMP. This could be a challenge for other businesses with digital ads when building with AMP.

How has AMP helped you improve speed?  

Rick: There was a symbiotic relationship between Axios and AMP straight away, because our mission is to help people get smarter, faster on the news and information that matters. AMP forced us to examine every feature, down to the byte, and make sure that it’s worthy of the reader’s time. It also pushed us to remove a lot of complex, client-side logic, which increased Lighthouse scores several-fold. 

How has AMP changed your perspective as a team of developers?  

Marissa: Being AMP-first means constantly flexing your creative muscles. We needed to re-evaluate how we wrote inline JavaScript. We need to be more mindful of the CSS that we are writing to stay under the 75kb limit. The AMP validator reminds us to always be thinking about the user’s experience. 

Rick: Exactly. If you only have one of each color in your pencil case, you start drawing a lot quicker instead of worrying about shades. 

Interested in learning more about AMP? 

Then sign up for our email newsletter to get the latest updates, event announcements, community discussions, and advanced tutorials straight to your inbox. 

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

The AMP Camp build process


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!

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, 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 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


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.


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


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=""></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=""></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 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=""></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="" /> </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: => 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]( 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, });


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