Posted by Jim Maclean on Feb 10, 2022

Get started with web performance

It makes logical sense that ‘faster’ pages are ‘better’ pages. They get things done faster, score better for SEO, use less energy and people like them. This is how I took a page important for SEO at mybuilder.com and cut its metrics in half.

What do we mean by ‘fast’

Page load is considered a ‘legacy metric’ at this point in the web performance community. This is because web pages are not just HTML anymore everything is parsed unpacked and many more things are downloaded. So in practice, a lower “page load” figure doesn’t necessarily correlate to a good user experience.

The first and largest contentful paint are two much better metrics to consider when trying to make a page that ‘feels fast’ as well as being objectively fast.

The first thing you should do is take stock of where your page is before you start. An easy way to do this is to use Chromes built-in Lighthouse feature or open up a SpeedCurve account and get familiar with the dashboard. This will give you an overview and a record of your progress, but to understand where to focus your efforts you will need details on exactly what is downloading, loading, rendering and requesting; and which order they are happening.

The Waterfall

You can get Chrome to draw a programme chart of a page’s traffic known in web performance circles as a ‘waterfall’ due to its down and to the right look when the table is sorted by its default ‘time started’ column.

To see the waterfall for your page navigate to it in chrome then: Open the network tab in the developer tools Check ‘Disable cache’ Pick a less than perfect network condition e.g. ‘fast/slow 3g’ then hit refresh on the page.

When the waterfall is complete you should be able to see how items are bunched together forming obvious bottlenecks.

Screenshot of the chrome dev tools network tab

As I am new to the world of web performance I had two main strategies to speed the page up and a third that was taken care of by the MyBuilder SRE team:

  • Have less stuff
  • Reorder the stuff
  • Faster servers

Have less stuff

Read the items on your waterfall from top to bottom. You will likely notice 3 common offenders putting wide plots on your chart: Images, Fonts and JavaScript.

Images

If an image is not visible for some reason try and hunt it down and remove the offending code. Also, consider removing any images that are not working towards the page’s goal.

Compress your images, if you can’t visibly see the compression artefacts on your jpegs you are not squeezing hard enough. You will see the nasty squares but real users probably won’t. For PNGs run them through a png compressor which takes the ‘lossless’ format and applies a gzip like compression to the bits which will make images with lots of flat colours dramatically shrink in file size. SVGs are often small by nature but can be slimmed down with SVGO.

Once you have compressed the images make sure you are serving appropriate sizes to smaller devices. At MyBuilder.com we used Symfony and a twig function to look at the request and conditionally serve smaller images to ‘mobile’ user-agents. You can also do this with modern ‘responsive image’ HTML, which is probably better but is out of the scope of this post. This newer HTML markup will give the browser a load of options and specs for a set of images so it can decide which one it wants depending on its current conditions.

Images in the HTML are requested in the first batch because the HTML comes back from the initial server request but images in the CSS are displayed only if they are deemed visible after parsing the DOM and CSSOM (which can be blocked or forced to be re-evaluated by scripts). What this means is images placed with CSS background images end up coming comparatively late.

Fonts

Webfonts slow down your page. Take a hard look and see if you need them all. Make sure the ones you do have are hosted from the same place as the rest of your assets. This will mean the client is already preconnected and can use HTTP2.

Javascript

This one is less straightforward. It means having a proper look at what you are including and how much of that you are using. If you have jQuery included that is a good place to start but the road to removing it is out of the scope of this article.

If you can’t remove a script make sure it is in the right place. The traditional logic is to have vendor scripts in the document Head and ‘custom’ scripts after the page contents. The reason for this is the scripts in the head will be downloaded and parsed before the page is rendered but scripts after that will be downloaded when the browser reaches that line, which will block the page parsing until the script has run. A more consistent way to achieve this is to add either ‘async’ or ‘defer’ attributes to the script tag. ‘Async’ will stop the blocking but still run as soon as it can and ‘defer’ will wait until the page has completed its parsing.

After you have removed discreet scripts. If you have a set of javascript which is bundled at build time you have the opportunity to add payload reduction into your build system. Webpack, Rollup, Parcel and other popular tools all have options for tree-shaking, code-splitting and scope hoisting.

Tree-shaking will analyse your code and only include functions or scopes that are called and will discard the rest. Great if you have something like Underscore.js included.

Code-splitting will split the compiled code into multiple bundles(files) which can be individually cached but more importantly, can be downloaded concurrently.

Scope hoisting is a newer method similar to module bundlers but will take the modules and put them into a single function after applying some re-naming to avoid conflicts. What this means is smaller files but also a more simple structure that uses less memory and can be parsed more quickly.

Re-order the stuff

The perception of a fast page improves when it looks ready to use sooner. Sometimes this means moving back an initial load to avoid showing something that is not ready to use.

Prefetch

When the background image starts to download the CSSOM has already been parsed and styles have already been applied and the page is rendering. This will appear as a space reserved for the image and it will pop in when it loads. Not only does this look bad but it pushes the ‘Last contentful pain’ further back in time.

To combat this background image pop-in we can make the browser aware that we will need this image sooner by adding a ‘prefetch link’. At MyBuilder.com the image we wanted to use was given to the Twig template to then put into a style tag, so we also added a prefetch link to the page head as well.

Preload

By default, the page will start to render the page before all the assets have finished downloading. In the case of the (large) webfonts this often causes a Flash of Unstyled Text (FOUT) which can cause a layout shift. To combat this you can tell the browser to wait until the font is loaded before starting by adding a preload link to make sure the client gets the asset before trying to render the page by adding a preload link to the asset.

<link rel="preload" href="https://www.example.com/assets/fonts/example-webfont.woff2" as="font" crossorigin="" type="font/woff2">

This will push back your initial render but will often produce a better user experience. You will have to make your own decision on the tradeoff.

Deferred loading

It is down to the browser to decide the order images need to be downloaded. Traditionally order was not consistent or practical, meaning images in the footer could take priority over one from the top of the page. Modern browsers are better at looking at where the images appear in the DOM but they are not perfect or consistent. Deferred loading makes the image invisible to the initial parse and then swaps it in using javascript after the page has loaded.

<img src="tiny placeholder image" data-lazyimg data-src="assets/images/real-image.jpg" />

This can be applied to any image you think won’t be needed for the initial render.

Lazy loading

Where Deferred loading applies a blanket rule where used, Lazy loaded tries to be smarter about when it loads the image. Essentially it is Deferred loading combined with an event listener that waits for the asset to scroll into view and then loads it. The tradeoff between the required javascript and the user experience will have to be considered though. On our page at mybuilder.com, we passed the image over to the lazy loader manually when the user clicked a button that brought it into view.

Faster servers

I have already touched on HTTP/2. This ‘newer’ protocol can download more things from a single host at once. In the past, the client would hit the limit quickly and have to wait before it could get more assets from the same host. To get around this a hosting technique called ‘Sharding’ distributed your assets on multiple servers so the client could get more at once. With HTTP/2 this logic no longer applies and you are better off having everything in one place. You must make sure that your providers/servers are configured correctly or the client will revert to HTTP/1. At MyBuilder we use Cloudfront as our Content Delivery Network (a.k.a. CDN), which will cache the content at relevant Points of Presence (a.k.a. POP). This means that users will be able to get the content from a server closest to them geographically.

Conclusion

I am still new to the world of web performance but after making the changes above the page was roughly twice as fast with a ‘first contentful paint’ going from 8.32 to 3.38 seconds.

A chart on speedcurve.com showing a chart with a big dip

Jobs at MyBuilder

We need an experienced software engineer who loves their craft and wants to share their hard-earned knowledge.

View vacancies
comments powered by Disqus