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.
TLDR; Take me to the tutorial
What are Accelerated Mobile Pages?
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 in action
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
:amp
MIME type
Add a new
:amp
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
http://localhost:3000/
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
http://localhost:3000/index.amp
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
application.amp.erb
layout to your
app/views/layouts
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:
-
http://localhost:3000
to see the regular HTML page -
http://localhost:3000/index.amp
to see the AMP page -
http://localhost:3000/index
– this should generate an error
To validate your AMP page open your browser at
http://localhost:3000/index.amp#development=1
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
app/controllers/about_controller.rb
and HTML view named
app/views/about/index.html.erb
Find the following line in your
config/routes.rb
file:
get 'about/index'
and replace it with:
get '/about', to: 'about#index'
Next, open
app/controllers/about_controller.rb
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:
-
http://localhost:3000/about
to see the regular HTML page -
http://localhost:3000/about.amp#development=1
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
app/helpers/amp_helper.rb
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
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include AmpHelper
end
Next, update your
About
view in
app/views/about/index.html.erb
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:
-
http://localhost:3000/about
-
http://localhost:3000/about.amp#development=1
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
app/helpers/amp_helper.rb:
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
app/views/layouts/application.html.erb
like so:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!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 the AMP format by adding the following method to your controller action or template file:
ampify_off!
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
<head>
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.
-
Open
config/webpacker.yml
and change theextract_css
entry from false to true i.e.extract_css: true
-
Restart your webpack development server with
bin/webpack-dev-server
Now let's add a couple of helper methods that will inline CSS in development and production. Open
app/helpers/amp_helper.rb
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
require 'open-url'
on the first line as this is needed to load the CSS pack from the Webpack development server using http.
Next, update the
app/views/layouts/application.amp.erb
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
http://localhost:3000/index.amp#development=1.
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.
https://mysite.com/index.amp)
to discover any errors or warnings.
Sample 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.