How to Get 100/100 Google Page Speed Score with Middleman and Nginx

Everyone hates slow websites. Everyone likes fast websites.

I recently worked on a Google AMP (Accelerated Mobile Pages) project, which was created to mitigate bloated websites from being at the top of Google results on their mobile web index. I personally think AMP is a bad thing for us but that’s beside the point of this article.

The point of this article is that you can get a better user experience by just building websites that are fast, and you don’t need to use AMP. But if you wanted to, the methods in this post could also be a good starting point for getting your pages into AMP with minimal effort.

I use the static site generator Middleman for the front end of the website you’re currently reading, and host it on a Digital Ocean Droplet in an Nginx server, so I’ll give some examples of how I achieved the perfect score in the context of this blog and its stack.

Google PageSpeed Insights

Google PageSpeed Insights is a tool Google provides to score the speed of a website based on specific metrics including:

Avoid landing page redirects

Eliminate render-blocking JavaScript and CSS in above-the-fold content

Enable compression

Leverage browser caching

Minify CSS

Minify HTML

Minify JavaScript

Optimize images

Prioritize visible content

Reduce server response time

I wanted a fast website, and I like to get 100% on things occasionally, so I set out on a quest to get elliotec.com the 100/100 score. So I got it. Now I’m going to tell you how to get it.

Page size

Before getting into any of these rules in particular, lets talk about page size in general. Make your codebase small. If you don’t need all of Bootstrap’s enormous CSS codebase loading on all your pages, use something lighter and customize up. If you don’t need a bunch of JavaScript on your page, like for a blog, then don’t put it on there. Having a few little fancy animations at the expense of hundreds of milliseconds is usually not worth it to a user. That said, if a core part of the brand or requirements put you in a place where heavy JavaScript is necessary, there are still strategies you can use to keep it slim and fast.

Client and Server Control

One thing you’ll need to get the 100/100 score is direct control of the front end assets and build tools around them. I used the static site generator Middleman to build this site with my configurations to increase the performance as much as possible. This website has virtually no JavaScript (other than the Squaresend plugin for email), but if I needed it I’d be handling it like the rest of my assets. Here’s the link to my full Middleman config.rb for your reference.

Additionally, you’ll need full control of the server hosting your files. I use Nginx on Digital Ocean when I want full control of my servers, and since it’s just static files I don’t actually have to setup a database or backend other than the Nginx piece which is where a good chunk of the work here is.

Breaking down the metrics

Let’s get into some details now.

Avoid landing page redirects

This first item is mostly self explanatory. You don’t want to land on a page and redirect a user as much as possible. This is relatively common when a desktop site redirects to mobile, and it is very expensive in terms of performance. If possible, build a site that is designed mobile-first and responsive to prevent necessity of redirects to mobile versions of the site.

Enable Compression

Here we already need control of the server. Since http supports sending compressed files over the network for faster transfers, we should take advantage of gzipping everything we can before it gets sent to the browser. It’s super easy to enable with Nginx and Middleman. In my global nginx.conf, I have:

#nginx.conf http { sendfile on; types_hash_max_size 2048; server_names_hash_bucket_size 128; include /etc/nginx/mime.types; default_type application/octet-stream; gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_min_length 256; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/x-font-ttf font/opentype image/svg+xml image/x-icon; }

And in my Middleman config:

# config.rb configure :build do activate :gzip end

Leverage browser caching

Caching allows the browser to keep downloaded assets for a set period of time so that it doesn’t have to keep downloading the same files over and over. This is again quite easy to do with Nginx by setting expirations for different filetypes, like how I have in my local nginx.conf for this website:

#expiry map map $sent_http_content_type $expires { default off; text/html epoch; text/css max; application/javascript max; application/x-font-ttf max; application/x-font-otf max; application/font-woff max; application/font-woff2 max; ~image/ max; } server { listen 80; listen [::]:80; server_name elliotec.com www.elliotec.com; expires $expires; return 301 https://$server_name$request_uri; }

You can also ensure you are caching only files that haven’t changed by setting unique asset hashes on the filenames, which is done in Middleman with a simple addition to the build config like so:

# config.rb configure :build do activate :asset_hash end

Minify assets

One of the more well known and simple page speed strategies is minifying your assets. Having a build step that minifies your HTML, CSS, and JavaScript is essential in modern web development, but it gets skipped a lot. You can drastically decrease your bundle sizes with minification. In Middleman, all you have to do is put a couple lines in your config.rb to do the work for you, like this:

# config.rb configure :build do activate :minify_css activate :minify_javascript activate :minify_html end

The CSS and JS minification comes free with Middleman, but you’ll have to add gem middleman-minify-html for the HTML piece which you definitely want even though it wont be giving you as large of gains as the other assets.

Optimize images

Images are usually the most expensive files for a browser to download as far as size and speed, so this is one of the areas that you can get some of the most performance gains for the effort. To start out, you’ll usually want to have images at their lowest viable size and quality. Tools like Photoshop have the ability to do a lot of powerful work around this, but you can optimize them even more in a build step using a tool like imageoptim. Of course with Middleman this is pretty effortless. First you’ll want to add gem 'middleman-imageoptim', git: 'https://github.com/plasticine/middleman-imageoptim', branch: 'master' to your gemfile, then:

# config.rb configure :build do activate :imageoptim end

Nice, you just shaved a ton of weight off those heavy images.

Reduce server response time

Google has a pretty strict threshold on the acceptable server response time - keep it under 200ms. For a static site like this, that is basically a non-issue. If I were hosting dozens of server blocks on this same Digital Ocean Droplet, they could be fighting for resources and slowing things down so I wouldn’t recommend that. If you have a backend doing DB queries and such, there are numerous other reasons why your server could be slower than the 200ms threshold and you’ll definitely want to spend time analyzing and improving that.

Prioritize visible content

Prioritizing visible content means that the above-the-fold content should be small enough in size to not require additional round trips to the server in order to render. This can be done by doing things like loading third party scripts later, and generally keeping the above-the-fold content small and fast. A lot of the other items in here will help with this, but sometimes it’s really difficult to get this one perfect, especially if you have images as the initial visible content.

Eliminate render-blocking JavaScript and CSS in above-the-fold content

This is probably the trickiest item on the list, and it’s related to the previous one. There are a few strategies I used to get this one to pass. One, if you require third party JavaScripts or add the async attribute to all script tags like this: <script async src="main.js"> , so that external JavaScript files aren’t blocking the loading and rendering of the other content and load asynchronously.

Then, inline your CSS and JS instead of making external resource calls to them. Doing this by hand would be terrible and ugly, but we have tools to do this for us. There’s a couple steps to get all your assets inlined on your pages.

You’ll first need to add sprockets ( gem 'middleman-sprockets' ) to your app because we are hijacking Middleman’s asset pipeline.

) to your app because we are hijacking Middleman’s asset pipeline. Then in your config.rb add the inline: true hash to each minify build step like this:

# config.rb configure :build do activate :minify_css , inline: true activate :minify_javascript , inline: true end

Last, in your layout template where you pull in your CSS (wherever your <head> tag is probably):

<style> <%= sprockets .find_asset ( 'site' ) .to_s %> </style>

and likewise for any scripts you want to inline, in <script> tags of course.

Finally, I was having a lot of trouble getting the Google Analytics script on my site to not be render-blocking. It was the last thing on the list that I hadn’t achieved, and I was pretty annoyed that I wasn’t seeing that 100/100 score. Since my real goal was the number, and it was clear that my site was pretty fast already, I found a silly hack online to trick the Page Speed Insights tool to ignore the Google Analytics script. If they can contradict themselves I figure I can do this, and You Can Too!

if ( navigator . userAgent . indexOf ( "Speed Insights" ) == - 1 ) { // Your Google Analytics Code Here }

All done!

There ya go. If you followed the instructions in this post for your own self-hosted static site, you’ll get 100/100 score on Google Page Speed. The speed is what matters and that’s good for everyone using your website, and it’s also gotta be good for some SEO, right?

Join the discussion on Hacker News.

Update 2018-01-26: This post has been translated into Russian at HowToRecover.me

Update 2019-3-23: This post has also been translated into Uzbek by Transparent Cliparts