Tips for Optimizing Page Speeds
Tips for Optimizing Page Speeds
I've been spending a lot of time optimising the page load performance of my website on both mobile and desktop devices. The bulk of my effort has been guided by the documentation listed by Google's PageSpeed Insights tool. I've documented some of the things that I've learned in the process.
Server
For the Docker container 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
There are a few simple ways that you can overall improve page performance of your website by tweaking a few configuration options that are defined by default in NGINX.
Use GZip Compression Rules
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.
Refer to the NGINX location block configuration below:
location ~* /assets/.+\.(?<extension>js|css|xml|json)$
{
gzip on;
gzip_static on;
gunzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_min_length 1100;
gzip_buffers 4 8k;
gzip_proxied any;
gzip_types
text/plain
text/xml
text/css
text/comma-separated-values
image/png
image/x-icon
image/webp
image/svg+xml
image/jpeg
image/gif
text/javascript
application/x-javascript
application/javascript
}
The rule does two things:
- Matches the applicable file extensions based on the incoming request, by using the regular expression pattern specified (
/assets/.+\.(?<extension>js|css|xml|json)
). - Depending MIME type recognised, it will then serve the request with the applicable gzip headers, ensuring that the static asset will be served as such.
Use Reverse Proxy Caching
If you are using NGINX as a reverse proxy - which is often the case for PHP-FPM servers - then you might want to consider adding rules for caching content to a local path on your NGINX instance.
Refer to the location configuration block below.
http {
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=STATIC:10m
inactive=24h max_size=1g;
server {
location / {
proxy_pass http://1.2.3.4;
proxy_set_header Host $host;
proxy_buffering on;
proxy_cache STATIC;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating
http_500 http_502 http_503 http_504;
}
}
}
Apply Cache Control Headers
I'm working on this section!
Automatically Serving WebP Optimized 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.
The above code snippet is an example of a NGINX "location block" that this 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.
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
.
You might also find this project interesting: SilverStripe Automatic WebP Image Conversion
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 Text-based 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 amount of unnecessary characters found in most JavaScript and CSS assets. Certain strategies in this approach include, but not limited to...
- Reducing symbol length - reducing the length or size of syntactically recognized keywords such as variable names, numerical values, and operators (e.g. inline
function
statements converted to lambda declarations). - Removing white-space characters, and condensing all interpreted text - Applicable in both JavaScript or Cascading Style Sheet assets; this ensures there are fewer characters (and therefore bytes) saved in the file, and served to a user's browser.
- Removing duplicates - Removing any potentially duplicate code that might be imported or loaded by libraries with shared dependencies.
- 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.
- "Tree-shaking" - Removing "loose" or unused code that is otherwise found in third-party libraries. Typically this is only done in production environments.
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.
Tooling - NodeJS and JavaScript
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...
Tooling - PHP
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.
Optimising 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 Automatic WebP Image Conversion
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 at a reduced size. 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 trigger SilverStripe to rasterize or resize the image. This behaviour or functionality can be overridden, so that the resulting resized image can be saved or compressed to a WebP image at the same time that it is being rasterized or resized.
Find below a screenshot of how the resulting image asset appears in the filesystem once it has been resized on disk.
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.
I've since added support for
- GIF Images
- Silverstripe 4.10
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" Loading 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.
Lazy Load Blocking Resources
Static assets such as images, cascading style sheets and JavaScript files are considered to be blocking resources when they are loaded by default. Where possible, it's important to ensure that these resources are loaded after text content and other DOM elements have. There are a number of methods that you can adopt for reducing the time it takes for the "First Contentful" Paint (FCP) to complete, depending on the type of static assets you are optimizing. The idea of "lazy loading" is simple. Only load static, blocking assets when they are required such as when the window has focus, or if the DOM element is made visible to the user.
Images
Most images on a page can be "lazy loaded", particularly ones that are being rendered as part of a home page carousel (i.e. revolving gallery of images). For instance, not every image in a carousel is displayed to the user at once (this is by design), so therefore they don't need to be loaded immediately. However, by default, a web-browser will automatically load all images regardless of whether they are considered visible (i.e. display: none;
or display: block;
)
Fortunately there is a convenient way of achieving this kind of asynchronous loading behaviour for images, by using a third-party JavaScript library named "lazysizes".
It can be loaded as part of your website by simply declaring it as part of your header as such.
<head>
<script src="lazysizes.min.js" async="true"></script>
</head>
Integrating it with existing templates and images is as simple as doing the following.
<!-- responsive example with automatic sizes calculation: -->
<img
data-sizes="auto"
data-src="image2.jpg"
data-srcset="image1.jpg 300w,
image2.jpg 600w,
image3.jpg 900w" class="lazyload" />
The original image asset might look like this.
<!-- responsive example with automatic sizes calculation: -->
<img src="image2.jpg" class="lazyload" />
Fonts, Stylesheets, and JavaScript
No additional third-party library is required for loading static assets that are fonts, CSS stylesheets or JS assets. Instead, depending on the kind of behaviour you are seeking, you can use one of the following specifiers as part of <link/>
or <script/>
statements when loading static assets.
Google's Third-party Libraries
Lazily loading third-party libraries such as the ones that are used by Google Analytics engine, and Google AdSense are a little bit more tricky. It should be noted that in doing this, it can incur edge-case behaviour should you continue to use and rely on the data that is recorded in Google Analytics.
Google AdSense
Observe the following code snippet.
<script type="text/javascript">
window.addEventListener('load', function() {
var is_adsense_load = 0
window.addEventListener('scroll', function() {
if (is_adsense_load == 0) {
is_adsense_load = 1;
var ele = document.createElement('script');
ele.async = true;
ele.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js'
var sc = document.getElementsByTagName('script')[0]
sc.parentNode.insertBefore(ele, sc);
//google_ad_client: "ca-pub-xxxxxxxxx",
(adsbygoogle = window.adsbygoogle || []).push({
google_ad_client: "$AdSenseID",
enable_page_level_ads: true
});
}
})
})
</script>
Typically such a code snippet would be placed in the <head> section of your page. If you look closely enough, you'll note that this script is in fact just a revised version of the one that Google asks you to integrate as part of your pages, but it ensures that the script is only loaded after the JavaScript event call back is fired when the window has loaded.
It constructs the <script>
element manually, and appends it to the page's Document Object Model (DOM). This code will only get invoked once the page has loaded (and therefore invokes the callback).
This works, but the caveat with this is that it may affect the accuracy of your statistics, as it will likely discount users who are only visiting your page for less than a couple of seconds. However, you may also not consider this to be of importance, so this would be a significant improvement if this is the case.
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
You stated that wonderfully.
<a href="https://payforanessaysonline.com/">pay for essay</a> pay for essays <a href="https://buycheapessaysonline.com/">pay for college papers</a> buy college essays
<a href=https://phdthesisdissertation.com/>best dissertation</a> dissertation abstracts <a href=https://writeadissertation.com/>what is a phd</a> dissertation writers
great writing 3 from great paragraphs to great essays https://essaywritingserviceahrefs.com
You actually reported this wonderfully!
<a href="https://helpwithdissertationwriting.com/">dissertation help services</a> phd paper <a href="https://dissertationwritingtops.com/">dissertation help services</a> dissertation def
Hi there i am kavin, its my first occasion to commenting anyplace, when i read this piece of writing i thought
i could also make comment due to this good paragraph.
Amazing stuff. Thanks a lot.
<a href="https://quality-essays.com/">essays for sale</a> buy essay <a href="https://buyanessayscheaponline.com/">order essay online</a> pay for essay online
Great information. Many thanks.
<a href="https://quality-essays.com/">pay to write paper</a> pay to write my essay <a href="https://buyanessayscheaponline.com/">buy essay</a> pay for essay
you're actually a good webmaster. The web site loading
pace is incredible. It sort of feels that you are doing any distinctive trick.
Furthermore, The contents are masterpiece.
you've done a great activity in this topic!
Fine info. Thank you!
<a href="https://essaypromaster.com/">write my paper for cheap</a> essay writers online <a href="https://paperwritingservicecheap.com/">paper writing</a> how to write a scientific paper
<a href=https://essaywritingservicehelp.com/>will writing service</a> essay writing service 3 hours <a href=https://essaywritingservicebbc.com/>personal essay writing service</a> online essay writing service
what a college essay should look like https://essaywriting4you.com
Your mode of explaining everything in this paragraph
is actually pleasant, all can without difficulty know it, Thanks a lot.
Perfectly voiced genuinely! .
<a href="https://essayssolution.com/">do my essay free</a> what should i write my college essay about <a href="https://cheapessaywriteronlineservices.com/">write my essay online</a> write paper for me
<a href=https://writinganessaycollegeservice.com/>essay help service</a> admission essay service <a href=https://essayservicehelp.com/>admission essay services</a> what is the best essay writing service
copy writing services https://buyanessayscheaponline.com
This is nicely expressed. .
<a href="https://topswritingservices.com/">college essays writing</a> best rated essay writing service <a href="https://essaywriting4you.com/">best paper writing services</a> college paper writing service