Closing the PageSpeed Gap

July 28, 2016 4 min read

My recent move to a Static site generator and full-site caching wasn’t enough to get a flawless score from Google PageSpeed Insights, so I kept on and finally achieved success. You probably don’t care, but I’m going to tell you about it anyway.

There haven’t been any frills around here; no jQuery except for lazy form validation on one-off pages, sparing use of imagery, lean markup and stylesheets. It’s mostly text. Even still, Google docked the site points because it used too much render-blocking JavaScript and CSS. "Prioritize Visible Content!," it pleaded.

The render-blocking JavaScript turned out to be Typekit’s allegedly-but-not-actually asychronous standard embed snippet, which I replaced with the "advanced" version and added a localStorage JavaScript utility to help minimize FOUT. The trickier part, and relatively new to me, was including a subset of CSS for above-the-fold page content and then asynchronously loading the rest. Despite my unease with the phrase "above the fold" in the context of web development, Google is (annoyingly) right: there’s no good reason to have a visitor waiting on styles that are mostly for things she’ll not see until further down a page or even further into the site.

Embedding a streamlined bit of CSS isn’t complicated, but knowing what to lift out and doing it in perpetuity creates a workflow problem. Most projects that I work on use a single combined stylesheet, and this change now requires maintaining two of them and one must always account for above-the-fold styles. Thankfully Andrew Welch pointed me to loadCSS and criticalCSS1, and Patrick Harrington offered helpful advice about handling FOUT/FOIT and incorporating criticalCSS into the build process.

Sculpin’s Twig search paths are still a bit confusing to me, but now my gulp build process uses the critical package to output a few different CSS files (for different layouts) in source/_layouts/criticalcss, and I can use a little bit of Twig to inline them:

{% if inlineCssFile is not defined %}
     {% set inlineCssFile = 'criticalcss/default.css' %}
{% else %}
     {% set inlineCssFile = 'criticalcss/' ~ inlineCssFile %}
{% endif %}
<style>
{{ source(inlineCssFile) }}
</style>

When I want to reference a different critical CSS set, I can just choose a different file from the sub template, like this for a blog post detail:

{% set inlineCssFile = 'blog-post.css' %}

Since nothing beats simply removing stuff that isn’t essential, I took a few bold steps:

  1. Removed Google Analytics entirely.
    I’m not promoting my brand or measuring conversions or even using insights to offer more to my audience. I’m just rambling here about things I’ve learned. CloudFlare still gives me broad pageview stats so I’m not completely in the dark, but now I have one less thing to check obsessively.

  2. Replaced my Typekit fonts with self-hosted ones.
    It took a long time, and lots of experimenting, to end up with Freight Text Pro, but I think it’s a clear improvement in terms of legibility, file weight, and caching performance. (No more Typekit pings or slowdowns!)

  3. Improved font loading.
    As Patrick pointed out, it’s infuriating to have to wait on fonts and styles to load if you’re on crappy wifi and just want to see text. For that reason, I’m now using typekit/webfontloader and dropping my fancy styles if the visitor’s waiting around for more than two seconds. It’s as easy as dropping timeout: 2000 into the config, and it seems to at least work well with simulated dial-up connections.

After a few typographic adjustments, fixing some long-standing annoyances, and sprinkling in just a hint of structured data, I think I’ll be content with the site for at least another week.


  1. The evolution and discussion of Scott Jehl’s loadCSS solution is pretty interesting.

***

Updated 6/16/19 at 6:24pm