Here’s a step-by-step case study on how we optimized a Shopify ecommerce site with a 24% improvement in page speed, through the use of MachMetrics speed tracking and the expertise of Eugene Chekan, one of our recommended speed consultants.

This challenge is unique since it’s a Shopify site we don’t have any control over server settings, and are very limited in what we can edit on the page – due to Shopify’s theme structure. Yet we were still able to move things around to get a decent speed improvement.

Our target today is Authority Kitchen, a Shopify-driven e-commerce store. We focused our efforts on two types of pages to inspect and optimize – a main page and a product page. Let’s start with high level performance overview and then dive into specific optimizations.

High Level Analysis

Let’s take a look at primary page load metrics:

awi speed optimization high level

Fairly low Time To First Byte (TTFB, orange line) trending flat around 0.7–0.8 seconds. This is a good news for us: authoritykitchen.com is built on Shopify platform and we can’t affect back-end server performance much. Quite a gap between Document Load (black line) and Page Completion (blue line) though! This is most likely due to lots of resources like JavaScript, CSS and images being loaded, and browser needs some time to process and execute all this stuff. Let’s see if that’s the case:

awi speed optimization resources

Wow, that’s a lot of JavaScript! Its parsing and execution by browser affects our Page Complete metric for sure, and we can’t do much about this except removing some scripts from the page completely. But does it affect our First Render Time?

JavaScript Analysis

Let’s take a look at how scripts are affecting page render with Chrome Developer Tools:

awi speed optimization javascript

There are 3 scripts that are loaded before browser begins to render page. Luckily, two of them — ga_urchin_forms and shopify_stats — are loaded asynchronously and don’t block page render in any way. Real problem here is the jQuery <script> tag: there’s no async or defer attribute and browser loads script content in a blocking fashion, postponing page render until script is fully downloaded and executed.

What is render-blocking?

Here’s how browser renders a page: it loads HTML and turns tags into Document Object Model (DOM), builds CSS Object Model (CSSOM), DOM and CSSOM are then combined and all visible elements are rendered. When browser stumbles upon JavaScript or CSS during initial HTML parsing it pauses parsing and rendering until JS/CSS is downloaded and parsed. Why? Because JS can change HTML elements — even delete them or turn them into HTML comments — and CSS can make elements invisible or change their position. So browser has to download all external resources before proceeding and actually render any content. That’s why those external resources called render-blocking. The easiest way to ‘unblock’ rendering is to set async or defer attribute at the <script> tag. For CSS, it would be inlining critical parts of CSS right into HTML with <style> tag and loading all other styles from external stylesheets after the page is rendered.

So how should we solve jQuery being render-blocking issue? We can’t make it load asynchronously because some inline scripts are using jQuery and we’re not in control of those scripts. Although we can move jQuery as far down from <head> as possible so browser could render some content before blocking because of jQuery. In this particular case jQuery library isn’t used until main content section which gives us opportunity to render page header and menu.

There’s one caveat with this approach: browser will not download jQuery until HTML parser reach jQuery <script> tag. And we moved it quite far in the document. Sure, we improved first render time, but we’re still wasting a lot of time waiting for jQuery to be downloaded and parsed. You might think if there only was a way to tell browser to preload some important resources right away! And there is such a way: <link> tag with rel=’preload’ attribute.

To wrap up, here’s what we should do:

  1. Move jQuery as far down the document as possible.
  2. Place <link rel=’preload’> that points to jQuery as close to the start of the document as possible.

Let’s do this and take a look at how things are:

awi speed optimization js waterfall

Cool, looks like jQuery being preloaded right away without blocking render until it is used later in the document! Note how the browser prioritized resources: the one we explicitly stated as important with <link rel=’preload’> is getting high priority, while others that are loaded asynchronously are having low priority.

Low-priority JavaScript

There’s another thing we can do with JavaScript to speed things up, those low-priority asynchronous scripts. We can move a whole bunch of them to the end of the document, that is, right before closing </body> tag. This way browser will not waste time on executing scripts before page is fully rendered.

CSS

There’s another side of render-blocking resources issue, CSS. It is much easier to deal with compared to JS optimizations though: move styles to the end of the document, adding them to DOM with requestAnimationFrame if possible, and inline critical CSS in the <head> tag. Let’s start with the easiest one, critical CSS.

Critical CSS

Simply put, critical CSS are the styles needed to render above the fold content correctly. You then inline these styles in document’s <head> allowing browser to render content without blocking on waiting for stylesheets download whatsoever. There’s a plenty of tools for critical CSS generation, critical being a weapon of choice for me.

So, we have our critical CSS now, let’s inline it and move forward with postponing external stylesheets until page is rendered.

RAF hack

Here’s a small snippet that will add external stylesheets to the DOM only after something was rendered:

<noscript id="deferred-css">

    {{ 'theme.scss.css' | asset_url | stylesheet_tag }}

    {% include 'google-fonts' %}

</noscript>

<script>

    var loadDeferredStyles = function() {

        var addStylesNode = document.getElementById("deferred-css");

        var replacement = document.createElement("div");

        replacement.innerHTML = addStylesNode.textContent;

        document.body.appendChild(replacement)

        addStylesNode.parentElement.removeChild(addStylesNode);

    };

    var raf = requestAnimationFrame || mozRequestAnimationFrame ||

            webkitRequestAnimationFrame || msRequestAnimationFrame;

    if (raf) raf(function() { window.setTimeout(loadDeferredStyles, 0); });

    else window.addEventListener('load', function() { window.setTimeout(loadDeferredStyles, 0)});

</script>

There are Shopify-specific styles inside <noscript id=’deferred-css’> tag, but you can replace those with your <link rel=’stylesheet’> and this snippet will work for you. What this snippet does:

  1. All styles are placed in <noscript> tag, so they will be automatically loaded if browser doesn’t have JavaScript enabled, i.e. no broken styles for AdBlock/NoScript users.
  2. If browser is modern enough (in this particular case, newer than IE10, see browser compatibility table at MDN), then styles will be appended to the DOM when browser will be ready to render something.
  3. If not, styles will be appended when the page and its resources are loaded.

Let’s take a look on how’s the page rendered now:

awi speed optimization first render

Great! The page begins to render right after jQuery is loaded and no single CSS file is needed for this. We now have only one render-blocking resource, jQuery, and that’s it. We can call it partially render-blocking because browser now can render part of the content without any external scripts or styles. This way of rendering is called progressive rendering and is considered a good thing, because users love seeing content ASAP, even its parts. Here’s the comparison between rendering filmstrip to illustrate this point:

awi speed optimization filmstrip

Note how page was rendered before: browser has to wait until external render-blocking resources are loaded and after that the whole page is rendered. Now, after we optimized render-blocking resources, the page is rendered step-by-step: header with menu, product info, product image. To further understand progressive rendering let’s combine Visual Progress graph with a filmstrip above while taking notes when resources are loaded:

awi speed optimization visual progress

Have you noticed that jQuery was loading earlier before optimizations? How come that page starts render faster now? Bonus points if you thought “well, that must be those CSS optimizations helping out”. You are correct on critical CSS moving the needle here, but there’s another trick: deferred scripts.

Asynchronous JS optimization

One important thing about Shopify: there’s a way for Shopify App vendors to add their scripts to the page alongside default Shopify ones. These scripts are added with async attribute, which is a good thing. But here’s the main difference between defer and async:

async scripts are evaluated at the first opportunity after they finished downloading. That is, browser will prefer evaluate asynchronous JavaScript rather than render some part of the page. On the other hand, browser will wait until DOMContentLoaded event before executing any deferred script.

So here’s what happened when low-priority scripts were loading in async fashion:

  1. Browser loads HTML and starts to parse it, requesting synchronous scripts with high priority and asynchronous scripts with low-priority.
  2. Rendering is blocked until all critical resources like CSS and synchronous JS are downloaded and parsed.
  3. While browser parser critical resources, low-priority asynchronous scripts are starting to arrive, getting at the front of the browser’s thread queue and postponing rendering tasks.
  4. Browser parses and executes asynchronous scripts.
  5. Rendering task is now getting some attention from browser and page is rendered finally.

Simply put, rendering is not blocked per se, it’s just browser doesn’t have any spare time to care about rendering because of async scripts.

To fix this, we replaced async attribute with defer explicitly saying to the browser “don’t bother about these scripts. Like, really. Do some render, load stuff you need, and get onto those deferred scripts when important stuff is loaded and rendered”.

Shopify doesn’t provide any interface to make these changes, but there’s always a number liquid shenanigans you can use. Just remember to test out things carefully.

CDN

Now, when most impactful issues are resolved, we’re left with other issue that are hard to approach on Shopify platform: back end speed. Generally speaking, Shopify back end performance is not bad at all, but it may vary from time to time. One way of fixing this is to serve content via CDN: this will give us early traffic termination and, as a bonus, some other tweaks like correct HTTP headers, namely, cache-related ones. Early traffic termination will reduce roundtrip times globally: US users will get content served from US CDN servers, Asia users — from asian CDN servers, and so on.

We chose Cloudflare for its wide edge servers network, overall robustness and some cool features like Page Rules that are available even in free-tier plan.

Interestingly enough, the only one resource that is served from authoritykitchen domain is the HTML page itself, all other resources are served from Shopify’s CDN domain. And the page itself is not cacheable by content delivery networks like Cloudflare, yet there’s a noticeable boost in TTFB:

awi speed optimization ttfb

Explanation for this is simple: there’s a lot of Cloudflare edge servers and their connectivity to surrounding data centers is much much better than that of the average network provider. It’s like you’re sitting with a laptop right next to the server, no matter where in the world you actually are.

Let’s talk about this a little further – because many people do not realize that CDN’s can improve TTFB. Cloudflare has a performance-oriented infrastructure and brilliant engineers, so they care a lot about transfer speed between data centers. Here’s what happens when there’s no Cloudflare in place:

Your pc <-> your data provider <-> unknown routes on the net (fair amount of them) <-> Shopify CDN and edge servers data center (likely, closest to your)
Here’s what happens with Cloudflare :
Your pc <-> your data provider <->  unknown routes on the net (lesser amount of them) <-> Cloudflare edge servers DC <-> Shopify DC.
The thing is that Cloudflare has wide network of Points of Presence worldwide and they throttle out all possible milliseconds when it comes for traffic between them and other data centers. On the other hand, when no major CDN is used, then all transfer speed depends on an unknown number of network providers. And their hardware and software configuration can be pretty bad. So using Cloudflare is like choosing the fastest and shortest available highway, and relying on basic Internet infrastructure is like taking countryside roads.

Overall Results

To wrap things up, here’s a couple of summary charts from MachMetrics showing the speed improvement over time:

Speed Index and TTFB improvements:

awi speed optimization speed

Page Complete and Doc Load improvements:

awi speed optimization page complete doc load

Overall we were able to shave off about 2.5 seconds of Page Complete time – that’s 2.5 more seconds that this ecommerce site’s visitors are going to spend looking at their products instead of waiting around. Win!

Are you using Speedy Site?

Website speed optimization with speedy.site ensures your WordPress site is loading fast throughout the day, around the world, and on various devices as well as passing Google's code web vitals.