Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save XTechnologyTR/a3b80471b87396ac76209af9745990af to your computer and use it in GitHub Desktop.
Save XTechnologyTR/a3b80471b87396ac76209af9745990af to your computer and use it in GitHub Desktop.
December 11 2016 - Obtaining a Perfect Score with Google PageSpeed Insights in WordPress

Obtaining a Perfect Score with Google PageSpeed Insights (in WordPress or other systems)

My personal blog (http://30.jonathanbell.ca) just got a little uglier and a little faster.

I recently set a lofty goal for my blog. I wanted the site to achieve a perfect score (100/100) with Google PageSpeed Insights. Google PageSpeed Insights is a (very picky) tool to help front-end people make their pages load quickly in the browser.

A lot of people will tell you that you don't need a perfect score from Google to have a fast and performant website. I would tend to agree – especially after executing these changes 😓. You don't need to have a 100/100 score in order to have a speedy, modern, and accessible website. Still, I was hungry to try for the 100/100 score! After I got going on this idea 100 seemed like the holy grail. I wanted it. I hungered for it. After a lot of fussing, I got it. Well, ok.. I scored 99/100 but you'll see why I lost one point in a minute.

So, without further ado, here are the changes that I made.

Caching

Probably the easiest thing to install is a WordPress caching system (if you are running WordPress) like Comet Cache. PageSpeed Insights was complaining about my server taking too long to respond so installing a cache and allowing compiled pages to be served from memory was an easy plugin install and something that I had been meaning to do for a while. 😎

Moving Scripts to the Bottom of the Page

Moving scripts to the bottom of the page is not a trivial thing to do in WordPress. Most of all, you’ll probably want jQuery loaded in the of your document before other plugins load and try to call jQuery. PageSpeed Insights won't like this. Loading scripts at the bottom of the page is better for performance. In my case, it was safe to move all scripts (including jQuery) to the bottom of the page. Doing this was tricky, so I wrote a custom WordPress functionality plugin to do this. You could also add the following code to your functions.php file.

function mdwpbp_move_js() {
  // remove scripts from the WP header
  remove_action('wp_head', 'wp_print_scripts');
  remove_action('wp_head', 'wp_print_head_scripts', 9);
  remove_action('wp_head', 'wp_enqueue_scripts', 1);
  // re-add the scripts to the WP footer
  add_action('wp_footer', 'wp_print_scripts', 5);
  add_action('wp_footer', 'wp_enqueue_scripts', 5);
  add_action('wp_footer', 'wp_print_head_scripts', 5);
}

// enqueue the scripts
add_action('wp_enqueue_scripts', 'mdwpbp_move_js', 999);

I also used a plugin called MinQueue. MinQueue allows us to concatenate scripts and even CSS files into one file and place them in the header or footer of your WordPress installation.

Structuring HTML to Load Critical CSS First

Ug.. This was tedious. If you get this "error" from PageSpeed Insights, it means that you have CSS selectors in your CSS that are not being used to style "above the fold" content. PageSpeed Insights wants you to send the CSS selectors that are being used right away. In fact, it doesn’t even want to make another round-trip HTTP request to the server. In other words, "The page is painting! We need this CSS now!" Hence the term, "critical css". The Google wants the critical CSS delivered ASAP in order to reduce the number of HTTP requests and start painting visible parts of the page right away. This was a tall order to complete.

I don't know what other folks have done in this situation. Luckily, my CSS is pretty straightforward and I was able to simply inline it into the <head> tag of my WordPress pages.

$critical_css = file_get_contents(__DIR__.'/critical.css');

// Minify CSS if production environment.
if ($_SERVER['HTTP_HOST'] == '30.jonathanbell.ca') {
  // remove comments
  $critical_css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $critical_css);
  // remove space after colons
  $critical_css = str_replace(': ', ':', $critical_css);
  // remove whitespace
  $critical_css = str_replace(array("\r\n", "\r", "\n", "\t", '  ', '    ', '    '), '', $critical_css);
}

echo '<style>'.$critical_css.'</style>';

It’s extremely rough, but it suits my needs.

Remove Web Fonts in Favor of System Fonts

This was a tough pill to swallow but in the end, this boosted my PageSpeed Insights score the most. The custom web fonts that I was serving were very heavy “on the wire”. Since they were needed to paint “above the fold content” (see above) PageSpeed Insights didn't like that I was downloading so much "stuff" (aka assets) just to paint the page above the fold. The solution that you will find around the web is to use JavaScript to asynchronously load the font files and paint them to the page after the browser has downloaded them. This flies in the face of web design circa 2008/9. Even just 5 years ago, we were all really concerned about FOUC (Flash of Un-styled Content). That is, content that had painted to the page, but had not yet been styled. The content would be un-styled for just a moment while the browser received (and rendered) the remaining CSS and then elements and fonts would re-paint. People considered this ugly and would use hacks to actually hide everything on the page until all of the CSS was available and rendered and then (and only then) would JavaScript fire and reveal the page. I remember it like it was yesterday. Here is an example of how old is new. Note the FOUC on my CV site (using Google Web Fonts) and the JavaScript technique mentioned above.

Flash of un-styled content example

However, the fact is using additional resources/assets (such as fonts) on your site will slow it down. So, what to do? Remove those extra HTTP requests and lighten the load! That's right, I just went back in time, choosing to use just system fonts!

This made me and PageSpeed Insights very happy, actually. I didn’t need to worry about sending 3 different web-font file formats (in order to support older browsers) and PageSpeed was happy because I wasn't sending large files which would block the rendering of a page. Furthermore, I was happy because I didn't have to put up with a gross FOUC! The only downside to this is that you, as a web designer, will have less control over which font is used in your font stack to display your pages. 😒

For me, it doesn't really matter – I can give up some of that control in order to make large performance gains.

Set Far-future Expires Headers

PageSpeed Insights noted that I was not sending files with far-future expires headers. This meant binary and media files such as images and videos were not being cached by the browser for longer than a few hours. I made tweaks to my .htaccess file. Now, browsers will cache those types of files for about a month and won't request to re-download them if they already have them in their cache.

<IfModule mod_expires.c>

  ExpiresActive on

  # Set a default in case we are not explicit enough
    ExpiresDefault                          "access plus 1 month"

  # cache.appcache needs re-requests in FF 3.6
    ExpiresByType text/cache-manifest       "access plus 0 seconds"

  # Your document html
    ExpiresByType text/html                 "access plus 0 seconds"

  # Data
    ExpiresByType application/json          "access plus 0 seconds"
    ExpiresByType application/xml           "access plus 0 seconds"
    ExpiresByType text/xml                  "access plus 0 seconds"

  # Favicon (cannot be renamed)
    ExpiresByType image/x-icon              "access plus 1 week"

  # Media: images, video, audio
    ExpiresByType audio/ogg                 "access plus 2 months"
    ExpiresByType image/gif                 "access plus 2 months"
    ExpiresByType image/jpeg                "access plus 2 months"
    ExpiresByType image/jpg                 "access plus 2 months"
    ExpiresByType image/png                 "access plus 2 months"
    ExpiresByType video/mp4                 "access plus 2 months"
    ExpiresByType video/ogg                 "access plus 2 months"
    ExpiresByType video/webm                "access plus 2 months"

  # Webfonts
    ExpiresByType application/x-font-ttf    "access plus 1 month"
    ExpiresByType application/x-font-woff   "access plus 1 month"
    ExpiresByType font/opentype             "access plus 1 month"
    ExpiresByType image/svg+xml             "access plus 1 month"

  # CSS and JavaScript
    ExpiresByType application/javascript    "access plus 1 week"
    ExpiresByType text/css                  "access plus 1 week"

</IfModule>

This feels good.

The Results!

I almost scored a perfect score however one file couldn’t be cached like above because it was out of my control. I use Disqus on my site via a plugin. The plugin uses a JS file that is hosted on Disqus that does not have a far-future expires header set. Since this file does end up in the HTML output of my site, it's counted against my perfect score. If I remove/disable the plugin I score 100/100. Anyways, this was a fun exercise in jumping through hoops. Would I recommend this madness to my worst enemy? No. However, if your site is having some front-end problems, running it through a PageSpeed Insights test and implementing some of the suggestions couldn't hurt (much).

Here are my final trophies screenshots.

A PageSpeed Insights screenshot

A PageSpeed Insights screenshot

A PageSpeed Insights screenshot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment