Mathieu De Keyzer's face

 Improve Skeleton's performances with Google PageSpeed Insights

HTTP enthusiasts

Goals

Let's try to improve the Elasticms Skeleton's performances and so also improve the referencing by google (and other search engine) of my website with Google PageSpeed Insights.

Just frontend best practices

At the end, with this post, I'll would like to make some PR in Elasticms GitHub project but the first PageSpeed Insights's recommendations are about frontend developments.

From an indexing standpoint, it's always better to display your content as soon as you can. You should add an asyncor a defer attribute to your script tags. Also do not forget to put your script tags at the end of the dom, just before the closing </body>.

<script type="javascript" src="{{ asset('bundles/emsch_assets/js/app.js') }}" defer></script>

Those 2 attributes:

- `defer` will wait the loading of the page before downloading the script
- `async` will run the script as soon as the script is available


For the CSS, it's a bit more tricky. They have to be located in the header; the only attribute that works is the disabled. You realize that, with that attribute, the CSS won't be loaded at all. Here come the trick:

<link rel="preload" href="{{ asset('bundles/emsch_assets/css/app.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ asset('bundles/emsch_assets/css/app.css') }}"></noscript>

In this example, if JavaScript is supported by the client, the CSS will be loaded after the onload event.

The problem with that approach is that the page will quickly display the page without CSS than switch to its final apparance. This issue is know as Cumulative Layout Shift (CLS).

We should keep this technique to load CSS rules that doesn't affect the page's rendering. I.e. the print CSS:

<link rel="stylesheet" href="{{ asset('bundles/emsch_assets/css/app.css') }}?{{ hash }}">
<link rel="preload" href="{{ asset('bundles/emsch_assets/css/app.css') }}?{{ hash }}" as="style" onload="this.onload=null;this.rel='stylesheet'"  media="print">
<noscript><link rel="stylesheet" href="{{ asset('bundles/emsch_assets/css/app.css') }}?{{ hash }}"></noscript>

Remind you that you should also try to keep request counts low as possible. So your CSS finally looks like:

<link rel="stylesheet" href="{{ asset('bundles/emsch_assets/css/app.css') }}?{{ hash }}">

Yes, I know.

Remove unused CSS

When we start with Bootstrap we usally include everything, but we may want to include only what's needed:

@import "bootstrap-variables";
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/root";
@import "~bootstrap/scss/reboot";
@import "~bootstrap/scss/type";
@import "~bootstrap/scss/images";
@import "~bootstrap/scss/code";
@import "~bootstrap/scss/grid";
//@import "~bootstrap/scss/tables";
//@import "~bootstrap/scss/forms";
@import "~bootstrap/scss/buttons";
//@import "~bootstrap/scss/transitions";
//@import "~bootstrap/scss/dropdown";
//@import "~bootstrap/scss/button-group";
//@import "~bootstrap/scss/input-group";
//@import "~bootstrap/scss/custom-forms";
@import "~bootstrap/scss/nav";
//@import "~bootstrap/scss/navbar";
@import "~bootstrap/scss/card";
//@import "~bootstrap/scss/breadcrumb";
//@import "~bootstrap/scss/pagination";
//@import "~bootstrap/scss/badge";
//@import "~bootstrap/scss/jumbotron";
//@import "~bootstrap/scss/alert";
@import "~bootstrap/scss/progress";
//@import "~bootstrap/scss/media";
//@import "~bootstrap/scss/list-group";
//@import "~bootstrap/scss/close";
//@import "~bootstrap/scss/toasts";
//@import "~bootstrap/scss/modal";
//@import "~bootstrap/scss/tooltip";
//@import "~bootstrap/scss/popover";
//@import "~bootstrap/scss/carousel";
//@import "~bootstrap/scss/spinners";
@import "~bootstrap/scss/utilities";
@import "~bootstrap/scss/print";

$fa-font-path: "~@fortawesome/fontawesome-free/webfonts" !default;
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import "~@fortawesome/fontawesome-free/scss/brands";
//@import "~@fortawesome/fontawesome-free/scss/solid";
//@import "~@fortawesome/fontawesome-free/scss/regular";

//@import "~aos/dist/aos.css";
@import "main";

Even with that config you'll drag way too much CSS rules. PageSpeed Insights continues to complain.

Now we move print rules into a dedicated CSS file:

@import "bootstrap-variables";
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/print";
@import "main_print";

Serve static assets with an efficient cache policy

This recomandation is about improve the HTTP's headers. As, with the skeleton, we can very easily generate unique asset url by adding the assets archive's hash as query parameter:

<script type="javascript" src="{{ asset('bundles/emsch_assets/js/app.js') }}?{{ hash }}" defer async></script>

Ensure that you add the hash parameter to all assets references. Now we can increase the max-age from 24h (default skeleton value) to 2 years by defining this environment variables:

APACHE_CACHE_CONTROL='max-age=63072000, public'

You can also add an immutable flag.

APACHE_CACHE_CONTROL='immutable, max-age=63072000, public'

Enable text compression

Finally, and it's probablly the only one, here is a PR to make.

We need to add the mod_deflate Apache module and acivate it for text response in the Skeleton docker image. Just a small commit.

If you aren't using the elasticms's docker image you have to activate it in you server's config.