AMP on Rails

Matthew Ciampa
Making Dia
Published in
7 min readMar 22, 2018

--

At Dia, we’re constantly trying to improve our user experience and a big part of it is serving our pages faster to customers with slower network connections. Since we serve customers throughout the US, and not just in major cities, many of our users are on mobile devices that still use 3G connections. There’s also the case of a potential customer on the subway or out in the country where availability can be limited. So what can we do about that?

Our main goal is to create a better first-page experience for our customer, and in order to do that, we need to take a closer look at our load times and how we can improve upon them. First, incrementally bring down the load time of our existing experience without having to re-write too much HTML/CSS. Second, create a mirror of our landing page using AMP to serve our organic mobile users from Google search results at a much faster rate.

What is AMP? ⚡

AMP is an open sourced Google project centered around providing Accelerated Mobile Pages to users through search. AMP is a way for Google to cache a static version of your page to serve up quickly to mobile users in search results.

AMP pages have strict rules that attempt to blacklist anything that could have a negative performance implication. It removes the ability to use things like !important in CSS and disallows any JavaScript that isn’t run within the context of an AMP component. If you’d like to learn more, check out their docs.

First Pass Before AMP

The way our static assets are built vary depending on where you are in our experience. Little more than half of our views are in React and bundled through webpack and split by view. The rest still lives within Rails’ asset pipeline, which is only bundled once for all views. That means, on our homepage, we’re serving those beefy application.js and application.css bundles on each page-load. They weren’t too bad, application.js hovered around 112kb uglified + gzipped and our application.css around 55.4kb. What if we built separate bundles just for the homepage? We could maybe just create a homepage webpack bundle should only include the things we need? Do we really need jquery, lodash, etc. for our JavaScript and on our Sass side, we probably can rid most of our dependency on bootsrap3 and in-app component styling?

What we ended up with was pretty staggering in file size change. It turns out we didn’t need ~111kb of that JavaScript bundle and ~50% of the CSS bundle. Our home.js webpack bundle now hovers around 1.4kb gzipped and our CSS bundle around 22kb! That represents a 98% decrease in JavaScript file size!

Our main bottleneck with load, and still is today, has to do with our images. We have a lot of them, each with a lot of color information. We did another pass on compression and reducing file size where we could. The problem that we’re still facing today, is due to the fact that all of our images are loaded through our CSS file and that makes it hard for us to make some really good optimizations without having to do a markup re-write. Following this article, we plan to defer image load for assets outside of our viewport and do a few other cool things to help boost our load times.

From the perspective of a customer on a 3G connection, this probably still doesn’t mean the difference between a bounce and her staying and learning about all the awesome things we do! AMP to the rescue!

How We Implemented AMP

Since we’re running our landing page through Rails, we’ll need a way for Rails to distinguish between the AMP version of our page and our normal HTML page. Easy enough, we decided to simply create a new mime-type that aliases text/html. This an easy addition to your mime_types.rb file:

Mime::Type.register_alias('text/html', :amp)

This has the following benefits:

  • Preserve the same action to distinguish between an AMP page and non-AMP version of the same page so we can simply call the page with format=amp to serve us the AMP version.
  • Create separate layout files and templates for AMP pages. We’ll need to include AMPs JavaScript and CSS.
  • Allow us to add number of AMP pages later if we need to.

CSS became one of the larger earlier challenges for us. AMP requires you to write all of your CSS inline in the head through a <style data-custom/> tag that also has a file-size limit. The problem is, all of our CSS is defined through Sass and if our goal is for both the AMP and non-AMP to mirror, there’s no sense in forcing ourselves to re-write all of our CSS within our head, 🤢! We decided to bite the bullet and output the contents of our built CSS bundle into this tag via a small helper method:

def amp_stylesheet
asset =
if Rails.env.development?
Rails.application.assets.find_asset("path/to/asset")
else
File.read(
Rails.root.join(
"public#{stylesheet_path("path/to/asset")}"
)
)
end

raw(asset)
end

This ended up working out well for us. We had access to our style guide CSS, retain the file structure/separation and ability to still use our production compilation step during CI builds.

Being able to think about the page as mobile-only not only feels amazing (because that never happens 🙂), it provides you with a different set of rules that you need to follow when constructing the markup for these types of pages. For example, in the below image, we only need to deal with column A. The problem with how we setup column B is around having the image load within our CSS. Doing this, we’re not able to defer the loading of the image. Why not take advantage of <amp-img /> with it’s lazy-loading and responsive capabilities?

Having so many images on our site, this ended up being an even larger win for us. Now all images except for those above the fold will be deferred in loading until they’re scrolled into view!

Pitfalls to Avoid

With all this fun stuff, there were definitely a few road bumps along the way that we needed to iron out before we could really get this page live. There are just some things that will start to get clunky in working with AMP and it’s expected; It’s a completely different workflow and adheres to a very strict set of rules that must be followed or else the page will not be indexed.

JavaScript

Firstly, you cannot write your own JavaScript and it will prevent Google from indexing your page if any exist. All JavaScript needs to live within the context of AMP ‘components’. You need to ensure that there are is no javascript lingering behind feature flags, analytics or error reporting tools.

We ran into a few cases of this. New Relic, for instance, will insert JavaScript at the head of your page only within — for our case — a production context. It’s use cases don’t apply since the page is not within the app so we we’re able to just remove it. That might not always be the case, though.

Analytics

All analytics will need to run through AMP’s <amp-analytics /> component that whitelist different providers (their list is not very long 🙂). With pixel tracking, it’s a little more lenient but worth a read through the docs before getting started.

Testing

Sometimes things don’t always work as intended and we as humans are more than prone to creating bugs! The problem with AMP is that, since the page is indexed and the search result handles the redirect, reverting things isn’t immediate and straightforward. If you feel it’s necessary, like we do, create a feature flag to disable the view-ability of the AMP page along with the related canonical urls in the case of a severity 1 bug. This can be the difference in time between just waiting for Google to kick off the cache removal and de-index or waiting for a pull request and CI build to run and deploy along with the cache removal.

Finally, general testing is a very different experience with AMP — especially if the non-AMP version of that page is rendered via the server. It’s important to always test flows in a faux cached environment that lives away from your application. AMP doesn’t know and doesn’t care about your main application session, behavior or view rendering since the cached page will live elsewhere. We simply Curl’d our in-app version of the page and uploaded the outputted HTML to another server and tested all of our different possible flows.

Ultimately, AMP cut our load times in around half on average and we’re already starting to see the benefits. In contrast, the updates to our existing page have seen nominal results with around the same amount of effort being put into both — but we’ll keep iterating on this.

If you love tinkering with the performance tab and all things JavaScript and CSS, we’d love to talk to you about joining our exceptional team!

--

--