Luc Shelton

Tips for Optimizing Page Speeds

Tips for Optimizing Page Speeds

Posted 4 months ago Created 4 months ago 11 Minute(s) to read 0 comments

Overview

As you might have noticed, I resurrected my website after I (accidentally) destroyed the previous installation. It was unfortunately beyond repair, and at the time, I was more concerned trying to get it ported to Docker containers for convenience and maintainability. For this iteration of my portfolio, I decided to go out of my way to optimize page load times, which meant improving performance in several areas. This included the overall performance of the Docker containers, how the website served to the end-user, and improving how the underlying framework (SilverStripe) behaves while doing so.


Server

For the Docker image, my biggest concern were largely to do with CPU usage and memory footprint. I am making use of a all-encompassing CMS framework called SilverStripe which (fortunately) ships with some additional features for caching to either a local filesystem, or to a distributed caching system such as Redis or Memcached. However, it does come with some added bloat, and often requires me to install a lot of additional extensions for PHP in order for it to work correctly.


NGINX Configuration

There are a few simple ways that you can overall improve page performance of your website by tweaking a few default configuration options with your NGINX server.

Implement GZip Compression for Static Files

By default NGINX only serves text/html as with gzip compression. You'll note that if you navigate to a file of any other type than a simple .html document, that the response headers won't specify the Accept-Encoding header with gzip as the corresponding value. This can be tweaked within the scope of a location configuration block.

For instance, within the location block for my SilverStripe installation's NGINX configuration, I've defined a wider range of assets that can be supported for compression.

Defining Cache Control Headers

I'm working on this section!

Automatically Serving Compressed Image Assets

Depending on how your project is developed, it's possible to write a configuration for NGINX so that it automatically attempts to send the .webp version of an image to the requesting user, instead of the original image asset itself. The benefit of this approach is that if your server-sided software automatically converts your image assets to .webp once they are uploaded, or rendered by the server, the optimized version of the asset will be used by NGINX, and the original asset will be used as a fallback should there not be one.

map $http_accept $webp_suffix {
  default   "";
  "~*webp"  ".webp";
}

location ~* /assets/.+\.(?<extension>jpe?g|png|gif|webp)$ {

...

    # more_set_headers 'Content-Type: image/webp';
    add_header Vary Accept;
    sendfile on;
    try_files "${request_uri}${webp_suffix}" $uri =404;

...

}

Which translates to logic that can be otherwise described by this flowchart diagram.

A flowchart diagram illustrating the logic used by the NGINX instance with this location block configuration

A flowchart diagram illustrating the logic used by the NGINX instance with this location block configuration

Above is an example of a location block that my website uses for serving publicly visible image assets to the requesting user. It's doing several things here, but most importantly the try_files statement at the bottom of the location block is attempting to do one task. If the .webp counterpart for the image that is being requested exists, then serve that back to the requesting user of the website. If it doesn't exist, then simply use the original version of the asset as a fallback.

Providing that you have integrated the NGINX location block correctly with your website, HTTP responses for image assets should be appearing in your developer tools as such.

A screenshot from Google Chrome's Developer Tools displaying a response from a HTTP request for an image asset on the website.

A screenshot from Google Chrome's Developer Tools displaying a response from a HTTP request for an image asset on the website.

You'll notice that the Content-Type header is now resolving image/webp as opposed to the image assets actual MIME type which would typically be image/jpeg or image/png.


Website Performance

Improving the performance of your website itself is a significantly more complex subject, as it is of course largely contingent on which tools you use for developing your website with in the first place.

Minify your Scripts and JavaScript Static Assets

Most modern Single Page Application frameworks such as Angular already minify and "bundle" JavaScript and CSS static assets, but if you're like me and not using a SPA framework such as Angular, then you will need to seek other tooling. "Minification" quite simply is the process of reducing the text content of JavaScript and CSS assets. Certain strategies in this approach include, but not limited to...

  • Reducing the length of syntactically recognized keywords such as variable names, or operators.
  • Removing all white-space and condensing all interpreted text in a JavaScript or Cascading Style Sheet so that there are fewer characters (and consequently bytes) in the file.
  • Removing any potentially duplicate code.
  • Bundling - combining multiple JavaScript files into a single one so that it reduces the amount of HTTP requests to the server for individual JavaScript assets.

Fortunately there's already an abundance of tooling available, and you don't need to waste your spare time on the weekend reinventing the wheel.

NodeJS and JavaScript Tooling

Much to what I was alluding to previously about SPA frameworks, there's already a significant amount of JavaScript tooling available for minifying and bundling JavaScript and CSS assets. I personally recommend the following libraries, as they have been suitable for my projects...

PHP Tooling

If you're like me, and you're using reluctantly using PHP because your website's backend is powered by it (ahem, SilverStripe), you will probably be seeking alternatives to what has been mentioned above.

Optimizing Image Assets with WebP

As described on the official website, "WebP is a modern image format that provides superior lossless and lossy compression for images on the web". It's an low-memory image format that is widely compatible with most modern browsers and has a significantly reduced on-disk memory footprint with minimal reduction in overall image quality.

Depending on how your website is developed will ultimately determine how best to implement the usage of this image format, but the tooling available is widely compatible with most (popular) operating systems including Linux, OS X, and Windows. Furthermore, the tooling is largely compatible with other existing image formats such as JPEG, PNG, and GIF.

In general, if your website makes use of some form of backend for rendering pages (i.e. Server-Sided Rendering or SSR for short) or for serving an API for data retrieval, you should be attempting to automatically rasterize or convert image assets to WebP image file format where possible.

You can read more about WebP image format here.

SilverStripe and WebP

SilverStripe doesn't have any official support for the WebP image format, but there are community members that have developed extensions for automatically generating .webp assets for popular formats including JPEG and PNG when SilverStripe attempts to render images in smaller sizes. This largely depends on how you have developed the templates for your SilverStripe website, but arbitrary template statements used for resizing images such as $Image.ResizedImage(200, 300) and $Image.Fit(300,300) will cause for SilverStripe to rasterize or resize the image. This particular functionality can be overridden at framework-level, so that the resulting resized image can be saved or compressed to a WebP image instead.

Find below a screenshot of how the resulting image asset appears in the filesystem once it has been resized on disk.

A screenshot of how the newly resized or rasterized images appear on disk once they are converted to the .webp image file format.

A screenshot of how the newly resized or rasterized images appear on disk once they are converted to the .webp image file format.

If you are using the SilverStripe framework for your website, then I strongly recommend that you use a plugin or add-on such as the one that was originally developed by a community member by "nomidi". It's a SilverStripe add-on that seamlessly integrates the ability to automatically generate .webp images if the WebP PHP extension is loaded and available for usage on the server.

If you are using PHP (or SilverStripe), you can make use of this simple PHP code snippet for testing whether the WebP GD image library extension is loaded and available to use.

<?php

if (function_exists('imagewebp'))
{
    echo 'webp support available';
}
else
{
    echo 'function not found';
}

echo phpinfo();

As it may be possible to infer from the code, it simply checks whether or not the function "imagewebp" is loaded and available globally.

I made a fork of this add-on that only does one change, and that's to save newly created *.webp images as *.jpeg.webp instead of *_webp.jpeg. The reasoning behind this modification is simply so that it's easier to produce an NGINX configuration that can construct a path to the .webp version of the image instead of the original. This constructed file path is then of course used for loading and serving the .webp image asset to the user (if it exists on disk).

You can find my fork for this add-on here.

WebP Image Format Compatibility with PHP

In the event that imagewebp functions are not available for usage with your PHP Docker container, can make use of the following snippet as part of your Dockerfile for building PHP FPM containers. These snippets assume that you are making use of the Alpine Linux variant of the PHP FPM container.

The first snippet ensures the system-wide dependencies for creating .webp images are available, and that the supporting PHP extension can create .webp images. The second snippet makes the function listed above available for usage within PHP, so that it can actually access the system-wide tooling for creating and converting assets to .webp.

Install packages and dependencies of WebP. This is only applicable if you are using both Docker, and the Alpine Linux image variant of PHP FPM.

RUN apk add --no-cache libwebp-dev libwebp

Next, the GD library has to be configured so that it points to the relevant paths for the PNG, JPEG, FreeType, and WebP system libraries.

RUN docker-php-ext-configure gd \
    --with-freetype-dir=/usr/include/ \
    --with-jpeg-dir=/usr/include/ \
    --with-png-dir=/usr/include/ --with-webp-dir=/usr/include/

Lastly, you will of course want to run your container with the aforementioned modifications, and test that the required modules are loaded and are accessible.

Improving First Contentful Paint Speed

In summary, "First Contentful Paint" is the amount of time it takes for any DOM element to be rendered to the screen. This doesn't necessarily mean the entire page must be loaded, rather it simply means that a single element has begun rendering, and that the browser has acquired the all required assets to render the page with. In order for the browser to begin rendering, it must first download all required assets. These are typically defined in the <head> element of the page as <script> and <link> statements. Unless defined otherwise, these statements or HTML elements are considered to be "blocking", therefore meaning that the rest of the page cannot be loaded, or rendered to the screen until the assets defined in those tags have been downloaded by the browser. As you might imagine, this can potentially significantly impact the "First Contentful Paint" (FCP for short) speed.

Structured Usage of Async and Defer Statements

In some instances it could be the case that not all script or CSS assets need to be loaded in order for the page to be rendered to the user. Instead, they could be deferred or done asynchronously while the remainder of the website or page is being rendered or displayed to the user.


Tools for Testing Performance

Once you have made the necessary modifications for optimizing your website for improved load times, you can make use of some of the following websites for testing page load times, and for identifying any other errors.

Miscellaneous Links

You may also find these other links useful.



Comments

Comments