Accelerated Mobile Pages (AMP) with Ruby on Rails tutorial

Porsche Santos
Apr 16 · 10 min read

A short tutorial on how to incorporate Accelerated Mobile Pages (AMP) into your Rails application. This tutorial will also show you how to inline CSS with Webpack and test your AMP pages in production.

Image for post
Image for post

What are Accelerated Mobile Pages?

TLDR; Take me to the tutorial

AMP pages are basically web pages on a diet — they’re slimmed down versions of regular HTML pages.

AMP pages are a mobile-first search strategy with the primary aim of speeding up page load times on mobile devices. Google crawls your AMP page and stores it in a cache where it can be retrieved extremely quickly.

These pages show up on Google’s SERP (Search Engine Results Page) and are preloaded in the background. So when you tap on an AMP page link the page will load in an instant.

Learn more about the AMP project.

What does an AMP page look like?

In addition to designing and building our mobile-first Elastic Teams website, we also spent a little time turning key pages into AMP pages. Here’s a demo of the home page:

AMP pages in action
AMP pages in action
AMP pages in action. Source: https://elasticteams.io

Demo: See the AMP version or the HTML version of this article

Are AMP pages worth implementing?

This really depends on the kind of site you have, and whether your budget permits the investment to create an AMP version of your site.

If you’re a news publisher or have a lot of frequently visited blog content, then it only seems natural to take the AMP route. Google does give importance to AMP pages when it comes to organic search. So, you could enjoy a ‘boost’ when it comes to mobile search.

Another deciding factor whether or not to adopt AMP is the page load time of your current site. Google discovered that 53% of mobile users will abandon a site if it takes over 3 seconds to load. More alarmingly, Google’s research also revealed that it takes an average of 22 seconds to fully load a page on a mobile device.

Keeping these stats in mind, it’s a good idea to first focus on optimising your existing site pages to load fast on mobile devices rather than jumping straight into AMP. It can be argued that AMP should be viewed as the icing on the cake rather than a go-to solution for underperforming mobile sites.

So, does your site suffer from slow loading times? Find out by testing it on Page Speed Insights.

AMPifying your Rails application

While there are a couple of AMP gems that could have been used as basis for this tutorial, these gems are overkill for what we need — which is a simple implementation that avoids adding further dependencies to a Rails project.

This tutorial assumes that you have an existing Rails 6 app to work with and Webpacker gem installed.

1. Add new MIME type

Add a new alias to the text/html MIME type so that AMP templates and partials can be distinctly rendered. Remember to restart your Rails server for the change to take effect.

# config/initializers/mime_types.rb
Mime::Type.register_alias 'text/html', :amp

2. Add a new route for the AMP version of the home page

The root page of your regular HTML site can be accessed via

But, how do you access the AMP root page? The answer is simple: add a new route that responds to the aliased :amp format so that the AMP version of the home page can be accessed via

Rails.application.routes.draw do
root to: 'home#index'
# ensure AMP home page is accessible via http://localhost:3000/index.amp
# but not through http://localhost:3000/index or index.html
get '/index', to: 'home#index', constraints: lambda { |req| req.format == :amp }
end

3. Add a new layout for your AMP application

Next, add a new layout to your folder.

Assuming you intend to have regular HTML pages in addition to AMP pages, include a canonical reference to the regular HTML page so that Google can make the correct association when crawling your pages.

<!doctype html>
<%# <html amp> is also valid markup %>
<html >
<head>
<title><%= yield :title %></title>
<meta charset='utf-8'>
<%# main AMP library %>
<%= javascript_include_tag 'https://cdn.ampproject.org/v0.js', async: true %>
<%# include a canonical reference to regular HTML page. %>
<%# important: nil format ensures that url is rendered as http://localhost:3000/ rather than http://localhost:3000/index %>
<link rel="canonical" href="<%= url_for(:only_path => false, format: nil) %>">
<meta name='viewport' content='width=device-width,minimum-scale=1,initial-scale=1'>
<%# standard AMP styles %>
<style amp-boilerplate>
body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}
</style>
<%# fallback %>
<noscript>
<style amp-boilerplate>
body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}
</style>
</noscript>
</head>
<body>
<%= yield %>
</body>
</html>

4. Create a new AMP home page

The markup for your regular HTML home page should look something like this:

<% provide(:title, 'Regular page') %>
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

Now create the equivalent AMP page:

<% provide(:title, 'AMP page') %>
<h1>AMP Home#index</h1>
<p>Find me in app/views/home/index.amp.erb</p>

To test the pages, point your browser to:

  • to see the regular HTML page
  • to see the AMP page
  • – this should generate an error

To validate your AMP page open your browser at and check the browser console (a hard page refresh might be required). You should see the following:

Powered by AMP ⚡ HTML – Version 2004030010070 http://localhost:3000/index.amp#development=1
validator.js:6501 AMP validation successful.
validator.js:6501 Review our 'publishing checklist' to ensure successful AMP document distribution. See https://go.amp.dev/publishing-checklist

All good so far, except that you will notice that we are using a different view for each format.

This approach will work fine if your content is going to vary considerably between regular HTML and AMP views. However, in most cases your content will be similar, if not the same, so this set-up does not lend itself to easy maintenance as a change in content will demand the modification of two distinct views.

A better approach is to use a single view for both formats.

5. Create a shared view for both regular and AMP formats

To illustrate this, let’s create a new About page for both regular and AMP formats:

bin/rails g controller about index

This will create a new controller named and HTML view named

Find the following line in your file:

get 'about/index'

and replace it with:

get '/about', to: 'about#index'

Next, open in your editor and change as follows:

class AboutController < ApplicationController  layout proc { |controller| controller.request.format == :amp ? 'application.amp' : 'application.html' }

def index
render 'index', formats: [:html]
end

end

This code dynamically selects the appropriate layout depending on the requested format and renders the regular HTML version of the About page for both formats.

To test this out, try the following:

  • to see the regular HTML page
  • to see the AMP page and view the console log

Now we’re using a single view for both formats — nice!

Since AMP restricts what HTML elements we can publish on our page, we need a mechanism to conditionally show or hide AMP elements on the page. The best way to accomplish this is to use a helper method so that we can dynamically switch-in or switch-out content blocks in our view depending on the requested format.

6. AMP view helper

Firstly, create a new helper named containing the following code:

module AmpHelper  def is_amp?
request.format == :amp
end
def is_html?
request.format == :html
end
end

Include the helper module in your

class ApplicationController < ActionController::Base  include AmpHelperend

Next, update your About view in with the following markup:

<% provide(:title, 'About - Shared view') %>
<h1>About#index</h1>
<p>Find me in app/views/about/index.html.erb</p>
<% if is_amp? %>
<amp-img src='https://placekitten.com/600/400' layout='fixed' width=600 height=400>
<% else %>
<%= image_tag 'https://placekitten.com/600/400', size: '600x400' %>
<% end %>

With any luck you should see a furry friend on these pages:

7. Let Google know about your AMP page

In order to make your AMP pages crawlable by search engines you will need to make a few adjustments to your regular HTML layout. But before we do that, let’s add a few helper methods to

module AmpHelper  def is_amp?
request.format == :amp
end
def is_html?
request.format == :html
end
def ampify_off!
@ampify = false
end
def ampified?
if @ampify.nil?
true
else
false
end
end
def canonical_amphtml
return unless ampified?
capture do
tag.link(nil, { rel: 'amphtml', href: url_for(only_path: false, format: :amp) })
end
end
end

Then modify the regular HTML application layout like so:

<!DOCTYPE html>
<html>
<head>
<title><%= yield :title %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%# add canonical page for regular html page %>
<link rel="canonical" href="<%= url_for(:only_path => false) %>">
<%# add canonical reference for AMP version of page if require %>
<%= canonical_amphtml %>
</head>
<body>
<%= yield %>
</body>
</html>

The code on line 10 ensures that the default canonical reference is included in the page. The next line generates the canonical reference to the AMP page. These two lines of code will render the following HTML:

<link rel="canonical" href="http://localhost:3000/">
<link rel="amphtml" href="http://localhost:3000/about.amp">

In the event you do not require an AMP equivalent of your regular HTML page, you can deactivate this by adding the following method to your controller action or template file:

For example, when used in a controller action:

class ServicesController < ApplicationController  def index
ampify_off!
end
end

Or inside a view template:

<% ampify_off! %>
<% provide(:title, 'Services page') %>
<h1>Services#index</h1>
<p>Find me in app/views/services/index.html.erb</p>

8. Inlining CSS with Webpack

One of the restrictions AMP imposes on our implementation is that all stylesheets must be inlined in the of the page using the following AMP construct:

<style amp-custom>
/* css code goes here */
</style>

In production, this is easily accomplished using this code excerpt:

<style amp-custom>
<%= File.read(File.join(Rails.root, "public", asset_pack_path('application.css'))).html_safe %>
</style>

However, replicating the same behaviour in your development environment is a little trickier and requires a small hack.

  1. Open and change the entry from false to true i.e.
  2. Restart your webpack development server with

Now let’s add a couple of helper methods that will inline CSS in development and production. Open in your editor and add the following code to the module:

require 'open-uri'module AmpHelper  def is_amp?
request.format == :amp
end
def is_html?
request.format == :html
end
def ampify_off!
@ampify = false
end
def ampified?
if @ampify.nil?
true
else
false
end
end
def canonical_amphtml
return unless ampified?
capture do
tag.link(nil, { rel: 'amphtml', href: url_for(only_path: false, format: :amp) })
end
end
def webpack_inline_css(filename)
filename = filename + '.css'
if current_webpacker_instance.dev_server.running?
open(inline_asset_url(filename)).read.html_safe
else
File.read(File.join(Rails.root, "public", asset_pack_path(filename))).html_safe
end
end
def inline_asset_url(name)
server = current_webpacker_instance.config.dev_server
protocol = server[:https] ? "https://" : "http://"
host = server[:public]
pack = asset_pack_path(name)
"#{protocol}#{host}#{pack}"
end
end

Don’t forget to include the on the first line as this is needed to load the CSS pack from the Webpack development server using http.

Next, update the file with the following code:

<!doctype html>
<%# <html amp> is also valid markup %>
<html >
<head>
<title><%= yield :title %></title>
<meta charset='utf-8'>
<%# main AMP library %>
<%= javascript_include_tag 'https://cdn.ampproject.org/v0.js', async: true %>
<%# include a canonical reference to regular HTML page. %>
<%# important: nil format ensures that url is rendered as http://localhost:3000/ rather than http://localhost:3000/index %>
<link rel="canonical" href="<%= url_for(:only_path => false, format: nil) %>">
<meta name='viewport' content='width=device-width,minimum-scale=1,initial-scale=1'>
<%# standard AMP styles %>
<style amp-boilerplate>
body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}
</style>
<%# fallback %>
<noscript>
<style amp-boilerplate>
body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}
</style>
</noscript>
<%# custom inline css %>
<style amp-custom>
<%= webpack_inline_css 'application' %>
</style>
</head>
<body>
<%= yield %>
</body>
</html>

And finally, test your page in your browser by visiting Don't forget to check the browser log for any errors or warnings.

9. Testing your AMP pages in production

Once your AMP pages are live, it’s a good idea to test them using the Google Search Console. Simply visit the AMP test site and submit your AMP page (e.g. to discover any errors or warnings.

AMP test results
AMP test results
AMP test results

Moving forward…

As you can see, incorporating basic AMP pages in your Rails application is a pretty straightforward exercise.

The real challenge lies in adapting your site to play nicely with AMP components. You can forget your familiar Javascript frameworks — these are largely unsupported in AMP so you will need to work extra hard to move features found on your regular HTML pages to your AMP pages.

A more robust approach for new projects is to focus on designing and building applications with a mobile-first strategy in mind. By doing so, adopting AMP becomes a much easier and natural undertaking.

The Startup

Medium's largest active publication, followed by +708K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store