Thursday May 6, 2021 By David Quintanilla
Improving The Performance Of Shopify Themes (Case Study) — Smashing Magazine

About The Creator

Carson is the co-founder of Archetype Themes and focuses on constructing the perfect person expertise in e-commerce.
More about

When coping with themes for giant platforms and CMS, legacy points typically develop into a bottleneck. On this article, Carson Shold discusses how his group improved the efficiency and group of their Shopify themes, and improved maintainability alongside the way in which.

The dreaded refactor of outdated code may be difficult. Code evolves over time with extra options, new or altering dependencies, or perhaps a aim of efficiency enhancements. When tackling an enormous refactor, what are the issues you must give attention to and what efficiency enhancements are you able to count on?

I’ve been constructing Shopify themes for the higher a part of a decade. After I labored in-house at Shopify in 2013, themes have been pretty easy when it comes to code complexity. The toughest half was that Shopify required themes to help IE8, and up till late 2020, IE11. That meant there was a number of trendy JavaScript we couldn’t make the most of with out generally sizable polyfills.

Eight years later, in 2021, themes are infinitely extra advanced as a result of Shopify has launched a ton of latest options (to go together with our in-house concepts at Archetype Themes). The issue is that constructing new performant options will solely go thus far when a few of your codebase is so outdated that it has outdated IE polyfills or IE10 CSS hacks. Our themes had fairly good velocity scores for a way a lot they supplied, however they have been undoubtedly bloated.

Our Purpose Was Easy

Higher efficiency throughout the board. Sooner time to first paint. Much less blocking JS. Much less code complexity.

Getting there was the exhausting half. It included:

  • Take away jQuery and rewrite ~6k traces of JS per theme in Vanilla JS
  • Take away Handlebars.js, as our templating wants have been means too small for such a big bundle
  • Standardizing code shared between themes (take away duplication)

Transferring away from jQuery was a blessing, however an extended course of. Fortunately, Tobias Ahlin has a unbelievable information on a few of the quick conversions away from jQuery. Whereas going by these modifications, it was the proper time to rethink some extra fundamental points like how my JS was structured and the way components have been initialized.

Take away jQuery

Writing Vanilla JS at all times appeared like a pipe dream. We needed to help outdated IE, so it was simply really easy to disregard any try at eradicating it. Then IE 11 help was dropped by Shopify and the clouds parted — it was our time.

Why take away jQuery anyway? I’ve heard a lot of arguments about this, similar to its bundle measurement isn’t that dangerous in comparison with a framework like React. Properly, jQuery isn’t a framework like React so it’s a little bit of a non-starter comparability. jQuery is a means of utilizing CSS-like selectors and developer-friendly syntax for issues like animations and Ajax requests. Most of all, it helped with cross-browser variations so builders didn’t have to consider it.

We needed to take away it for a couple of causes:

I’m a kind of builders who have been caught previously. I knew jQuery inside and outside and will make it pull off almost something I attempted. Was it good? No, in fact not. However whenever you take a look at the lifecycle of some JS frameworks that flamed out, jQuery has at all times been regular and that was acquainted and protected to me. Eradicating our reliance on it and untangling it from ~6k traces of code (for every theme) felt insurmountable — particularly after I couldn’t know for positive my efficiency scores would profit or by how a lot.

Our strategy was to remark out every module we had, take away jQuery, and slowly add in every module or perform separately whereas it was rewritten. We began with the only file, one with a couple of features and some selectors. Good and straightforward, no errors in dev instruments, time to maneuver on.

We did this one after the other, remembering the straightforward fixes from the early information once we received to the advanced ones like refactoring the entire potential options related to a product and its add-to-cart kind (I counted, it’s 24 distinctive issues). Ultimately, we received the product JS from 1600 traces of code to 1000. Alongside the way in which, we discovered higher methods to do some issues and would return and refactor as wanted.

We realized Vanilla JS isn’t scary, it’s only a bit extra of an intentional means of writing code than jQuery. We additionally realized some historic code was a multitude — we wanted to prepare the JS to be extra modular and take away duplicate code (extra on that beneath). However earlier than that, we needed to play with a few of the enjoyable JS we’d solely utilized in different tasks.

Intersection Observer API

Shopify themes are highly effective in that they let retailers transfer components across the web page nevertheless they need. Meaning, because the developer, you don’t know the place the ingredient is, whether or not it exists, or what number of exist.

To initialize these components, we had been utilizing scroll occasions that constantly checked if a component was seen on the web page with this perform:

theme.isElementVisible = perform($el, threshold) {
  var rect = $el[0].getBoundingClientRect();
  var windowHeight = window.innerHeight || doc.documentElement.clientHeight;
  threshold = threshold ? threshold : 0;

  // If offsetParent is null, it means the ingredient is fully hidden
  if ($el[0].offsetParent === null) {
    return false;

  return (
    rect.backside >= (0 - (threshold / 1.5)) &&
    rect.proper >= 0 &&
    rect.high <= (windowHeight + threshold) &&
    rect.left <= (window.innerWidth || doc.documentElement.clientWidth)

Though these scroll occasions have been throttled, there was a number of math being completed by the browser on a regular basis. It by no means actually felt too sluggish, but it surely did take up a spot within the call stack which impacted different JS competing for precedence. I want we had completed extra efficiency analysis on this replace particularly as a result of I believe it’s chargeable for most of the enhancements in Time to interactive and Whole blocking time that you simply’ll see beneath.

In comes the Intersection Observer API. Now that IE11 help wasn’t required, I used to be so completely happy to have the ability to totally make the most of this. In brief, it’s an asynchronous means of figuring out when a component is seen within the window. No extra sluggish measurements and scroll occasions.

To initialize a component when it’s seen, we use one thing so simple as this:

  ingredient: doc.querySelector('div'),
  callback: myCallback

All the JS required for the ingredient might be dealt with inside myCallback, stopping it from doing something till it’s seen.

This units up an observer for that ingredient, after which removes the observer as soon as it’s seen. It’s at all times good to wash up after your self even when you assume there may not be a lot impression with out it. If there’s a callback, we run it and our module is able to go.

theme.initWhenVisible = perform(choices) {
  var threshold = choices.threshold ? choices.threshold : 0;

  var observer = new IntersectionObserver((entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        if (typeof choices.callback === 'perform') {
  }, {rootMargin: '0px 0px '+ threshold +'px 0px'});


You’ll be able to move a threshold to initialize the ingredient earlier than it’s on the display too, which may be helpful if you wish to preload one thing like Google’s Map API barely earlier than the ingredient is seen so it’s prepared when it’s.

Layzloading Photographs And object-fit

We use lazysizes for lazy-loading our photos. It has some useful plugins for additionally loading background photos, however requires much more markup in your ingredient. Whereas the plugins are fairly small, it’s yet another factor that’s simply eliminated with pure CSS.

Utilizing object-fit in CSS meant that we may place a picture similar to a background picture, however as an <img> ingredient and get all the advantages of regular lazy-loading with out additional JS. The true profit in that is we’re one step nearer to utilizing native browser lazy-loading (which doesn’t help background photos). We’ll nonetheless must load in lazysizes as a fallback when the native strategy isn’t supported, but it surely means eradicating a whole dependency.

if ('loading' in HTMLImageElement.prototype) { 
    // Browser helps `loading`
} else {
   // Fetch and initialize lazysizes

MatchMedia API

Prior to now, we used enquire.js to know when breakpoints modified. That is used when resizing components, altering a module’s arguments for desktop vs cell, or just to point out/cover components that you would be able to’t with CSS.

As an alternative of counting on one other bundle, as soon as once more we will go along with a local resolution in matchMedia.

var question = 'display and (max-width:769px)';
var isSmall = matchMedia(question).matches;

matchMedia(question).addListener(perform(mql) {
    if (mql.matches) {
      isSmall = true;
      doc.dispatchEvent(new CustomEvent('matchSmall'));
    else {
      isSmall = true;
      doc.dispatchEvent(new CustomEvent('unmatchSmall'));

With just some traces of code, we will hear for breakpoint modifications and alter a useful variable that’s used elsewhere and set off a customized occasion that particular modules can hear for.

doc.addEventListener('matchSmall', perform() {
  // destroy desktop-only options
  // initialize mobile-friendly JS

Looking down duplicate code

As I discussed originally, we had slowly constructed options into our themes for years. It didn’t take lengthy for some components to be constructed out that have been form of like others, like a full-width homepage video and later movies in your product itemizing or a popup video modal.

YouTube’s API, for instance, initialized in another way thrice and had almost an identical callbacks and accessibility options constructed out per-module. It was a bit embarrassing we didn’t construct it smarter within the first place, however that’s how you’re rising as a developer.

We took this time to consolidate a lot of our modules to be standalone helpers. YouTube grew to become its personal methodology that each one sections from all of our themes may use. It meant refactoring by breaking it down into probably the most fundamental elements:

  • Default API arguments (overridable by the initializing module)
  • A div ID to initialize the video onto
  • ID of the YouTube video to load
  • Occasions (API is prepared, video state modified, and so forth)
  • Play/pause when not in view
  • Deal with iOS low energy mode when autoplay not supported

My strategy was to do that all on paper earlier than coding, which is one thing that at all times helps me kind out what’s integral to the module I’m constructing vs what’s customized by the dad or mum that’s initializing it — a division of labor if you’ll.

Now our three themes that initialize YouTube movies a complete of 9 alternative ways use a single file. That’s an enormous code complexity win for us, and makes any future updates a lot simpler for me and different builders which may contact the code. By utilizing this identical strategy for different modules whereas changing to Vanilla JS, it allowed us to maneuver almost half of every theme’s JS to a single shared module throughout all of them.

That is one thing that was invaluable to our group and our multi-project setup and may not be helpful to your tasks precisely, however I consider the method is. Serious about simplicity and avoiding duplication will at all times profit your undertaking.

We did the identical for slideshow modules (picture slideshows, testimonials, product web page photos, announcement bars), drawers and modals (cell menus, cart drawers, publication popups), and plenty of extra. One module has one function and can share again to the dad or mum solely what’s required. This meant much less code shipped, and cleaner code to develop with.

Efficiency Stats

Lastly, the good things. Was this all price it? Most of this was completed blindly with the idea that much less JS, smarter initializing, and extra trendy approaches would lead to quicker themes. We weren’t upset.

We began all of this work with Motion, our first theme. It had probably the most bloated JS and the most important room for enchancment.

  • 52% much less JS shipped
  • Desktop dwelling web page speeds (with heavy components like a number of movies, featured merchandise, slideshows with giant photos)
Desktop dwelling web page Earlier than After Change
Lighthouse rating 57 76 +33
Whole blocking time 310ms 50ms -83.8%
Time to interactive 2.4s 2.0s -16%
Largest contentful paint 3.8s 2.6s -31.5%
Cellular product web page Earlier than After Change
Lighthouse rating 26 65 +150%
Whole blocking time 1440ms 310ms -78%
Time to interactive 11.3s 6.1s -46%
Largest contentful paint 13s 4.2s -67.6%

Then we moved on to Impulse, our second and most feature-heavy theme.

  • 40% much less JS shipped
  • 28% quicker cell dwelling web page speeds
Desktop dwelling web page Earlier than After Change
Lighthouse rating 58 81 +39.6%
Whole blocking time 470ms 290ms -38%
Time to interactive 6.1s 5.6s -8%
Largest contentful paint 6s 2.9s -51.6%
  • 30% quicker cell dwelling web page and product web page speeds
Cellular product web page Earlier than After Change
Lighthouse rating 32 45 +40.6%
Whole blocking time 1490ms 780ms -47.6%
Time to interactive 10.1s 8.3s -17.8%
Largest contentful paint 10.4s 8.6s -17.3%

When you might discover these numbers received so much higher, they’re nonetheless not nice. Shopify themes are handcuffed by the platform so our start line is already difficult. That could possibly be a wholly separate article, however right here’s the overview:

  • Shopify has a number of overhead: characteristic detection, monitoring, and fee buttons (Apple Pay, Google Pay, ShopPay). In the event you’re on a product web page with dynamic fee buttons you may be about 187kb of Shopify scripts vs. 24.5kb theme information. Most websites may have Google Analytics, and perhaps a Fb Pixel or different monitoring scripts loaded on high of all this.
(Large preview)

The excellent news is that these scripts are loaded pretty effectively and most don’t block the web page rendering a lot. The dangerous information is that there’s nonetheless a number of JavaScript loading on these pages which might be out of the theme’s management and trigger some flags on Lighthouse scores.

(Large preview)
  • Apps are an enormous bottleneck and retailer house owners, usually, don’t know. We routinely see outlets with 20+ apps put in, and even a easy app can drop your Shopify speed score by 10+ factors. Right here’s the breakdown of our Impulse theme with three apps put in.
(Large preview)

Right here’s an awesome case study on apps and their effect on performance.

We’re nonetheless within the strategy of ending these updates to our third theme, Streamline. Streamline additionally has another efficiency options inbuilt that we’re exploring including to our different themes, similar to loadCSS by Filament Group to forestall the CSS from being a render-blocking useful resource.

These numbers aren’t insignificant. It’s broadly reported that speed matters and even small changes can make big impacts. So whereas we’re proud of all of this progress, it’s not the top. Efficiency will proceed to be a dominant a part of our builds and we gained’t cease in search of extra methods to simplify code.

What’s Subsequent?

Efficiency is an ongoing problem, one we’re excited to maintain pushing on. A couple of issues on our checklist are:

Sources For Shopify Builders

In the event you’re constructing on Shopify, or wish to get began, listed below are some useful sources for you:

Smashing Editorial
(vf, il)

Source link