Improving Google Page Speed Insights Score: Minifying & Loading CSS

Posted:

To start, you essentially have to think about, and, later action splitting up your CSS into 2 seperate entities.  The first part will deal with loading in the initial styles that are required to make a page not look broken "above the fold" (god I hate that term when talking about digital stuff). The second part is to load in all your other styles with javascript!

Loading the Initial CSS (init.css)

To do this you can create a new scss file which will load in just the styles for anything that could be "loaded above the fold", as an example my init.scss looks like this:

You may have heard the term "Critical CSS" before, my method is essentially a very similar, though I think a little more reliable.

@charset "UTF-8";

$base-font-size: 16px !default;
@import "../../vendor/sass-rem/rem";

// These styles will be loaded in the head within a <style> tag
// This file needs to be as small as possible, only loading in the essential styles.

// These imports are not actual styles, just variables & mixins
@import "vars"; // Variables used throughout the site, widths as pixel values are commonly used (will not generate any code from being imported)
@import "mixins"; // mixins that could be used in any file (will not generate any code from being imported)
@import "fonts"; // A bunch of mixins that deal with custom fonts, their font weights and styles
@import "colours"; // colour variables used across the site

// globally used styles
@import "globals"; // global styles, for example, defining box-sizing, a ".center" class for "text-align:center;"
@import "styleguide"; // The site's core styles for the main HTML elements
@import "template"; // the styles for the body, full width and "row" class styles

// includes
@import "includes/_header"; // will always be above the fold
@import "includes/_super_nav"; /// will always be above the fold
@import "includes/_parallax"; // used on banners, so for the sake of saving it glitching worth loading first

// partials
@import "partials/_page_banners"; // if set will be at the top
@import "partials/_page_content"; // if no banner set, content will be at the top of the page.

The init.css file that is generated from this is then attempted to be injected inline within the head element. I use a very basic plugin which I've setup called "File Get Contents" as you can guess, it uses some basic php functions to do the following:

  • Get the contents of a file (who'da thought it!) - file_get_contents()
  • Get the current working directory - getcwd()
  • Check the file exists - file_exists())

Here is some example code showing how to utilise that, including some fallbacks for when it is disabled, so the init.css file still loads ok regardless

{# This utilises the FileGetContents craft plugin to inject inline styles for page speed - https://github.com/lexbi/File-Get-Contents-Plugin---CraftCMS #}
{% set initCssFilePath = "/resources/site/css/init.css" %}

{% if craft.FileGetContents is defined %}

    {% if craft.FileGetContents.fileExists(craft.FileGetContents.getCwd() ~ initCssFilePath) %}
        <style>{{ craft.FileGetContents.getContents(craft.FileGetContents.getCwd() ~ initCssFilePath)|raw }}</style>
    {% else %}
        <link rel="stylesheet" href="{{ initCssFilePath }}">
    {% endif %}
{% else %}
    <link rel="stylesheet" href="{{ initCssFilePath }}">
{% endif %}

Loading the rest (styles.css & plugin/vendor stylesheets)

To load the remaining styles for your webpage we will need to load them in with JavaScript. Loading it in the traditional way (by using a "link" element), it will cause page rending to be blocked, and, therefore increase load time.

In addition to the styles.css file, there is the matter of loading in our JavaScript Plugin stylesheets or third party (vendor) stylesheets. We can do all this with the same bit of code.

Essentially this JavaScript code uses an array of paths to the stylesheets, it creates "link" elements out of all of them and appends them to the "head" element. One thing to note is that I have found that dealing with "normalize.css" separately and prepending it to the "head" works much better because loading it in first at the top means that all other styles loaded after will overrule anything defined within that file. If it was the other way around, the styles in the normalize stylesheet would take priority.

var stylesheets = [
	"/resources/vendor/prism/prism-ocodia.css",
	"/resources/site/css/styles.css"
];
for(var i = 0; i < stylesheets.length; i++){
	var stylesheet = document.createElement('link');
	stylesheet.href = stylesheets[i];
	stylesheet.rel = 'stylesheet';
	stylesheet.type = 'text/css';
	document.getElementsByTagName('head')[0].appendChild(stylesheet);
}

// normalise to prepend everything so that all the other css rules are more important & therefore override them if needed.
var stylesheet = document.createElement('link');
stylesheet.href = "/resources/vendor/normalize.css/normalize.css";
stylesheet.rel = 'stylesheet';
stylesheet.type = 'text/css';
document.getElementsByTagName('head')[0].insertBefore(stylesheet, document.getElementsByTagName('head')[0].childNodes[0]);