Improving Page Speed for Norse Projects

How a few tweaks can more than halve the load time for an ecommerce fashion retailer's landing page.

Norse Projects is a clothing company that sell, amongst other things, very well made t-shirts. I was browsing around the site recently and noticed that it was kind of slow. Not horrendous - but noticeable. It felt like things could be sped up a bit.

If you only read one thing...

Here's the summary: I created a copy of a category page, made some performance improvements, and improved rendering times — dropping Largest Contentful Paint from 6.3 seconds to 2.7 seconds - an improvement of 57%.

Performance improvements like this have been shown to increase ecommerce conversion rates and revenue - and using rough annual revenue numbers, I've estimated this improvement could be worth somewhere between $840,000 and $1.26 million.

But before I get into the changes, I want to first cover why it's worth looking at things like this.

Why is page speed important?

Ecommerce customers are highly sensitive to site speed and there are lots of studies that show that improvements often lead to better conversion rates. Vodafone found that a 31% improvement to Largest Contentful Paint led to an 8% improvement in sales. The Trainline found that making things faster by 0.3 seconds led to customers spending an extra £8 million. As a site speed monitoring tool, our own internal data consistently shows that visitors that see faster loading times have a higher conversion rate.

Making your ecommerce site fast can improve your conversion rate across all your channels. It makes your paid ads more profitable, it helps you get more sales from your email list, it can increase the revenue that you get from organic traffic.

To a small extent, Google also prioritises faster loading pages when it comes to search rankings. And it affects Google's quality score when it comes to paid ads, so a slow loading site may end up paying more for their ads.

When you have an ecommerce site, making it fast almost always results in more revenue.

And it's not just about money. Faster loading pages have been shown to result in better user engagement too. The Financial Times made major speed improvements to their site and found users were up to 30% more engaged - visiting the site more frequently and visiting more articles while they were there.

Before we start

Importantly, this isn't to be negative about the Norse Projects developers. No site is perfect - and these sorts of issues are extremely common. The developers might also know about them already, but might not have the time or resources to fix them yet. This post is just to show how particular technical changes can result in a faster loading page.

For the purposes of this test, I'm going to look to speed up the men's t-shirt category page.

What are we trying to improve?

The highlighted image below is the Largest Contentful Paint element (or at least, it is for the viewport of an iPhone 12 - and probably will be for most mobile devices), so my main focus will be on having that get painted to the screen quickly. That said, I'm also keen to reduce the time it takes for any content to start being displayed (except for the flashing loading animation).

Remove the loading animation

When the page starts loading, it first shows a loading spinner - the words "Norse Projects" pulsating on the screen against a white background.

I can't remember who to attribute this quote to, but a loading spinner is an apology for a slow site. And in this case, it's also not required. That whole animated logo is in a <div class="loader"> - if you remove that, you can see the page start to render normally, without the appearance of a splash screen.

People are very used to seeing pages load incrementally. Having a splash screen to hide the appearance of the page loading in the background is unnecessary, and almost always ends up delaying important metrics like First Paint and Largest Contentful Paint.

Lazy load the product images below-the-fold

The next thing I noticed was a ton of images being downloaded straight away. Here's Chrome DevTools showing 169 images being downloaded on the initial load of the page.

All of these images add up to 5.5MB, and 5.5MB is a lot to download — especially if you're on a slower connection.

The men's t-shirt page has (at the time of testing) 106 products on, and each product thumbnail was being loaded immediately on page load. It's fine for the above-the-fold images to load immediately (and in fact, that's exactly what you want) - but images that are below-the-fold can be lazy loaded so they only load just before they're about to scroll into view.

One challenge here: all of these images are laid out using a background-image, which you can't lazy load natively using the loading="lazy" attribute. I solved this by turning this:

<div class="thumbnail" style="background-image:url(https://norse.centracdn.net/image.jpg)">
</div>
Into this:
<div class="thumbnail">
  <img width="627" height="940" loading="lazy" src="https://norse.centracdn.net/image.jpg">
</div>

There were a few small changes I needed to make to the CSS too - I added width:100% and height:auto to all of the thumbnail images.

And importantly, the width and height attributes (when combined with height:auto in the CSS) help prevent layout shifts, as it allows the browser to reserve the space for the image before it arrives.

Don't load hover state images on mobile

There's a further complication with these product images. Most of them have a hover state image, so if you hover over them on desktop, it'll show another image.

These hover-state images also all load on mobile, and are downloaded immediately on page load. But of course, you can't hover on mobile. These images end up being downloaded on mobile, but never shown.

This is a bit of a tricky one to fix. I figured out a solution using the picture element, but I'm still not totally sure that this is the ideal way to do it - so be careful with this approach.

<div class="thumbnail-hover">
  <picture>
    <source media="(max-width:450px)" srcset="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=">
    <img width="627" height="940" loading="lazy" src="https://norse.centracdn.net/hover-image.jpg"> 
  </picture>
</div>

This uses a CSS media query to display a very lightweight, transparent PNG as the hover-state image if the browser width is 450px or less, but will use the regular hover-state image for any screen that's larger than that. It's not perfect, and Norse might want to play around with the width setting there - but it works.

Have Cookiebot load async

Right now, Cookiebot is on the Norse Projects site in the <head>, and looks like this:

<script id="Cookiebot" src="https://consent.cookiebot.com/uc.js" data-cbid="81427d3b-f9ce-4947-877e-ca88c23e63df" data-blockingmode="auto" type="text/javascript">

That's a synchronous JavaScript request - and because it's in the <head>, it will be render blocking. Browsers will want to download the file before they start to show anything to the user. Ideally, we want that to not block rendering.

And luckily, it looks like Cookiebot supports a manual implementation, which allows you to load it asynchronously.

Adding the async attribute to the tag means that it won't block rendering anymore. In web performance terms, we call this removing it from the critical path.

Add nomodule to the polyfill request

There's a <script> request for polyfill.io in the <head> - and because it's a synchronous request (i.e. it doesn't have the async or defer attribute), it will be render blocking. All of that means that the browser will wait to download that file before it starts rendering anything to the page.

But most users don't even need that polyfill file. Polyfills exist to give legacy browsers functionality that modern browsers have - so if you're using a modern browser (like the majority of people), then rendering is being blocked for no reason.

So ideally, we want to serve that polyfill file to users on legacy browsers - but have modern browsers ignore that request. We can use the nomodule attribute for this.

The nomodule attribute tells modern browsers to ignore a particular script request. Legacy browsers, however, have no idea what that nomodule attribute is, so they ignore it and continue to download the script as normal. Legacy browsers will download the polyfill, but modern browsers will ignore it.

The new polyfill request now looks like this:

<script nomodule type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.20.0/polyfill.min.js">

Host the LCP element on the main domain

While the main domain is www.norseprojects.com, the images are served from norse.centracdn.com.

This means that the browser has to jump through some hoops to connect to this new domain - it needs to negotiate DNS and setup an SSL connection. This can take some time. On a 4G connection, it took just over 500ms, as seen on this WebPageTest waterfall - the thin green, orange and purple line.

Serving those images from www.norseprojects.com instead would save that connection time, and would allow the images to arrive that much sooner.

Typically you lose the benefit of having a CDN when you do this - so I recommend fronting the entire site with a content delivery network like Cloudflare. It lets you have the best of both worlds: everything - including the main HTML document itself - gets the benefit of a CDN and you can serve your assets from the main domain so you don't have to pay any connection costs with connecting to a third party domain.

Preload the LCP element

Finally, I updated the <head> to include a preload to make sure the first thumbnail image was prioritised.

This will vary from site to site, and it's not always something that I recommend - it's definitely possible to preload too many things, and you should do this with caution. In this case, I found that preloading the first thumbnail image improved Largest Contentful paint by about 400ms, so it felt like the right approach for this situation.

In the future, I'd like to start experimenting with Priority Hints when they fully roll-out in Chrome. Priority Hints would allow me to add importance="high" to the <img> to bump up the priority, which feels less like the sledgehammer option of using a preload. But that said, the preload seems to do a pretty good job here.

What are the results?

Here's a before and after, both tested on a Pixel 2 on a fast 3G connection - showing the existing page vs the improved copy:

These changes resulted in Largest Contentful Paint dropping from 6.3 seconds to 2.7 seconds — an improvement of 57%.

How much could this be worth?

Google's Vodafone case study showed that a 31% improvement in LCP resulted in an 8% increase in revenue.

Given that this is a 57% improvement in LCP, it's not unreasonable to estimate that the uplift would be higher than that - maybe closer to 12%.

It looks like Norse Projects annual revenue is roughly $21 million. We could make the assumption that half of that revenue comes from their site, and so an 8% to 12% improvement on $10.5 million would be somewhere between $840,000 and $1.26 million.

Here's the key takeaway: it's not just massive brands that would benefit from speeding up their site. Mid-size ecommerce brands can also benefit enormously from investing in improving web performance. And quite often, these sorts of improvements can be done cost effectively - often just in one dev sprint.

If you enjoyed this post and want to get more like this in your inbox, sign up to our newsletter. And if you have any questions, you can message me on Twitter:@davepeiris.

November 2021

Hello.

Thanks for reading. If you found this interesting, you might also like Blackbird - site speed monitoring for ecommerce. Try it for free today.