Javascript performance and site speed is something everyone is concerned about. Your boss, your clients, everyone will ask you to make their online store faster.
As an engineer, you’ll take on the task, but will often find yourself in a code mess. The truth is that there is no silver bullet for site speed. Everything you try only gives you a fraction of a second of improvement. Finding that big improvement is like a treasure hunt.
There are many resources out there that will help guide you to a solution, but everyecommerce platformhas their challenges. In this article, I want to propose an opinionated way to approach a site cleanup or refactor, starting with the unseemly: code organization. These six steps will help you refactor a Shopify site to improve performance for Shopify merchants.
Grow your business with the Shopify Partner Program
Whether you offer marketing, customization, or web design and development services, the Shopify Partner Program will set you up for success. Join for free and access revenue share opportunities, tools to grow your business, and a passionate commerce community.
Put yourself in the shoes of your client’s customers. What is the most important asset that you need to see in order to have a buying intention? Is it the ability to hit the buy button as soon as the page loads? Or is it the images showing what the products look like? In the majority of cases for ecommerce sites, it’s the images showcasing the merchant’s products that are most important—customers want to see what they’re buying.
This does change if the layout of the site is built with scripts, such as with single page apps, but for the purposes of this article, I will consider images the most important assets on a site.
Before we strip away any unused code or start moving things around, we need to first define where Javascript should be in any given document, using best performance practices. If you haven’t before, I suggest reading the following resources:
If you’ve read these recommendations and are still lost about how to apply these recommendations to a Shopify site, follow along.
It’s important to define where Javascript should be, so that we can spot where scriptsshouldn’tbe. There are only three document areas that Javascript should exist:
Before thetag
After thetag
Before thetag
Since we have identified images to be the most important asset, most Javascript should exist just before the
标签,你nless you have very good reasons that it shouldn’t. I will go through the reasoning for each script section.
"Be vigilant with where the script is placed even if you weren’t the one who put it there."
Scripts allowed in thetag
Every third-party vendor is going to tell you that their script needs to be at the very top of thetag, but you can give themthe Terminator stop hand. Scripts in thetag are render blocking. This means visitors will not be able to see your beautiful website until the browser finishes parsing the content in yourtag.
So what scriptsareallowed to be in thetag?
None.
The Shopify platform is a server-side framework usingLiquidtemplating. The moment the source document is downloaded, there should be enough to render the most critical first render view, without any Javascript library needing to fill the gap (other than external CSS styles).
This doesn’t mean that no website should have scripts in theheadtag. Here are some examples where the scripts has legitimate reason to stay in the head:
Single page apps. This is even better if the single page app is hydrated with server side render.
Inline scripts that don’t trigger the downloading of external scripts, and performs time sensitive operations, such as:
If there are scripts that do need to be in the head, most of these should be just before the end of the closingtag. This ensures the site is optimized forbrowser resource prioritization optimization.
One more thing. There are misconceptions around the usage of scripts withasyncordefer. A script containingasynctells the browser that this script can be executed out of order, which makes this script non-render blocking. This meansasyncscripts execute the code as soon as the script resource finishes downloading. Scripts withdeferpushes the execution of the Javascript to the end. However, both types of script declaration still impact the network bandwidth as downloaded resources, which we can leverage for performance to download the most important assets for first render.
Scripts allowed just after the
tag
Scripts in this area of the document are not the worst, but not the best either. We are still aiming for that first render view. The more we can push the download of external scripts later, the better. If you have a hero image or a collection of the latest products that you would like your visitor to see, these should take priority over scripts that your visitors will never see.
Browsers are systematic at resource prioritization. With scripts, browsers read the source document in a top-down order. If you have external scripts before the hero images, the external scripts will have higher downloading priority than the hero images.
Scripts allowed just before thetag
All scripts should be here.
This includes analytics scripts. What good are your analytics if your visitor’s browser can’t even get to this point of the page?
这是很重要的。没有一个基准,这是下一个to impossible to tell if anything you do improves performance at all. We can argue that there are improvements that would make a huge difference, but without proper measurements to understand what is happening, you could be making amazing upgrades that no one notices because they didn’t move the metric your boss is looking at.
To measure correctly, we will be usingperformance marks. This is a native Javascript API that is supported by most browsers in the world. Performance marks need to be in place right where they are executed. Specifically, we will be measuringbrowser parse duration: the time it takes for the browser to parse your Javascript.
We will also make use of performance metrics that Chrome specifically has already collected for us:paint.
performance.getEntriesByType('paint');
Let’s place the performance marks at the proper place. First, we need to understand how to measure parse duration. To do this, place performance marks like the following example intheme.liquid.
This should output a ton of metrics in ChromeDevTools. We’ll focus on the parse metrics that we just implemented.
The time measurements here all resulted from high resolution timestamps in milliseconds. We can see that the browser took 2563ms (2.6 seconds on Fast 3G) to parse whatever is in the head tag. We can also see that the first paint doesn’t happen until the browser finishes parsing the head at 3522ms (startTime 959ms + duration 2563ms).
What does this mean? This means that your visitor waited through two and a half seconds (minus network time) of white screen (aka nothing) before seeing your site.
Goal 1: Reduce parsing duration in the head tag
假设你不能删除任何脚本site, your goal should be shifting metrics to optimize for performance. The first paint metric is an important number to reduce for performance. However, what’s holding it back isn’t just what’s in the head tag—parts of the body tag are also responsible for it, since the browser needs to parse it too before it can be rendered. So, let’s also place performance markers within the body tag, like so:
Once this is added, we should see the following:
We can see from here that first paint is sometime after parsing the head, but before finishing parsing the body. If we can reduce the parsing duration within the body layout section, it should help bring the first paint value down.
Goal 2: Reduce parsing duration in the body layout
With these two goals in mind, let’s understand a little math here. If we cannot remove any lines of code in the project, what can we do to reduce the parsing duration in the head and body layouts? The answer is to push the parsing duration to body scripts near the end of the body. Let the browser parse what is important first, so that it can render it as soon as it can.
Duration (Benchmark)
Desired result
Head
2560 ms
Less time spent here
Body layout
2107 ms
Less time spent here
End body scripts
17 ms
More time spent here
First Paint
A timing within body layout
Faster
We are trying to change the parsing duration within each section to ultimality reduce the first paint metric timing.
4. Clean up the scripts that don’t belong anywhere
Imagine you’re moving into a new house. You usually don’t start by packing up the clothes you need to use everyday—you start with stuff you hardly ever use. Likewise, before we go and move all the script to the bottom of the page, let’s start with the scripts that don’t belong anywhere: scripts in the middle of the body.
Some of these scripts have dependency on some Javascript libraries, which makes it really hard to optimize for performance. It puts us at risk of breaking the site if we move these scripts to the bottom of the page without considering these dependencies. In Shopify themes, scripts in the middle of body includes any Javascript sitting in any Liquid files except for the layout Liquid files. So don’t try to fix everything at once—pick a battle and start there.
To make things a little easier, pick a page and start with the very first script you encounter after the opening body tag that is not in a layout Liquid file. Relax, it’s like playing a game of Pokemon when you first enter a grassy area.
First encounter
You found your first enemy—I mean, script. First, I want you take out your measuring tape and magnifying glass and really understand what this script is doing.
Benchmark a script
We will be doing exactly what we did earlier with performance marks, except now we will measure specifically for a given script, like in the following example:
The above example will measure the parsing duration of this particular script.Don’t forget to add new performance mark names in thetheme.liquidfile:
In the console log, we’ll see something like this:
We can see that the browser took about 12ms to parse. That doesn’t seem too bad. The key is determining how many of these we have. If we have 200 of these scripts roaming around, that adds two seconds of duration.
Finding script dependencies
Before we can migrate the location of this script, we need to find all its dependencies. These can be anywhere. Here are some clues to look for:
$('css_selector')JQuery.
Function calls to nowhere. Search where this function is declared.
{{ * }}Liquid tags.
Document the dependencies at the beginning of the script. This will come in handy when we start moving code around.
What to do when there are Liquid tags in the script
If the Liquid tag is a global tag, moving this piece of code totheme.liquidwill be fine. If the Liquid tag is associated with a particular template layout, wrap the script with the following:
{% if template == 'collection' %}
{% endif %}
If the script code involves complex dependency on the existence of code somewhere, this is where you would start refactoring the code so that you can move it out of the Liquid files.
Move that code!
Once you’ve identified all the script dependencies, it’s time to move the code. We’re aiming to move all the code to the end of the body tag. To maintain the original order of script execution(important in most cases, unless you figure out how to refactor whatever is there to be not order dependent), leave it just after the performance mark for body end scripts, like the following:
Progressively benchmark your changes
After you’ve moved your code, make sure you didn’t break the page by checking if what you expected to happen, has happened.
parse_head
start time
Parse
head
Parse_body
layout
Parse_body
end_scripts
First
paint
Room for improvement (ms)
Base
1090
2560
2107
17
4805
1155
Menu script
946
2546
2046
24
4661
1169
Compared to the base measurements, we can see the parse duration for the body layout has decreased and the parsing duration for the body end scripts has increased. We know the menu script has a 10ms parsing duration, so this is expected. The first paint has decreased as well, but it is too early to be conclusive of anything. We know that first paint can happen the moment the browser finishes parsing the head.
TheRoom for improvementcolumn is the number of milliseconds of delay from the end of the parsing head. It is a good indicator to tell us if we are optimizing between a 50 percent gain versus a 1 percent gain.
Room for improvement= First paint — ( parse head start time + parse head duration )
Rinse and repeat
Now, do the same thing for every script you encounter in the middle of the body.
Parse head start time
Parse head
Parse body layout
Parse body end scripts
First paint
Room for improvement (ms)
Base
1090
2560
2107
17
4805
1155
Menu script
946
2546
2046
24
4661
1169
Script 1
891
2569
2041
34
4622
1162
Script 2
928
2487
2039
78
4590
1175
Script 3
972
2540
2060
80
4690
1178
Script 4
1022
3147
2080
118
5066
897
Script 51
909
2561
1553
118
4716
1246
Script 6
923
2561
1428
128
4734
1250
Script 7
951
2557
1416
124
4746
1238
Script 8
921
2563
1325
152
4782
1298
Script 9
978
2556
1308
138
4825
1291
Script 102
1372
2568
1145
275
4052
112
This is truly a battle for the milliseconds. In the process of moving scripts to the bottom, some refactors were made so that the script in the middle of body is not dependent on the script’s location. In the results above, we can see that we have successfully reduced about one second of the parsing duration for the body and shifted some of that parsing to the body end scripts. This resulted in a relative 8 percent improvement for first paint time, which is about 320 ms.
5. The finishing move
It’s time for the final boss: moving all the scripts in the head to the end of the body, while maintaining the script order.
Parse head start time
Parse head
Parse body layout
Parse body end scripts
First paint
Room for improvement (ms)
Base
1090
2560
2107
17
4805
1155
All scripts
977
1193
3938
161
2199
29
Let’s understand what’s happening here. We moved quite a few external script requests from the head tag to the body tag. External resources that were initially render blocking are no longer blocking. This allows the first paint to happen much faster.
We have effectively improved the start render time by almost 40 percent, without losing any script functionalities.
It never hurts to do this. We achieved amazing numbers simply by shifting script around the document. Give it a run through theweb page test. In my case, I found a very interesting problem.
The page had decent first paint timing, but went back to blank until the ten-second mark. What happened?
Turns out, the site implemented theanti-flicker snippetfrom Google Optimize’s A/B experiment framework.
We have options to work around this. We can try moving this Google Tag Manager script back to the top of the head and see how it does.
老实说,我不认为这是值得的。它添加three whole seconds to the first paint, even if it’s the first resource to download, just for the potential that there is an experiment to run. There are better ways to instrument A/B experiments without hiding the entire document for potentially four seconds, which is exactly what this anti-flicker snippet is doing.
This issue actually explained why I was not seeing progressive improvements as I moved the scripts out of the body. It was always delayed by this anti-flicker snippet.
Grow your business with the Shopify Partner Program
Whether you offer marketing, customization, or web design and development services, the Shopify Partner Program will set you up for success. Join for free and access revenue share opportunities, tools to grow your business, and a passionate commerce community.
Success! We have improved start rendering time, all without losing the scripts that we need to keep. Fixing site performance is up to all of us. As Shopify continues to improve site performance on the store front, every developer should do what they can to improve site performance as well. It benefits everyone.
Helen is a Web Development Manager at Shopify. Engineer at heart, people in mind, wizard for those around her, and reads minified code for fun. Catch her traveling to be as close as possible to the world’s natural heritages.