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

About The Writer

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

When coping with themes for giant platforms and CMS, legacy points typically grow to be 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 best way.

The dreaded refactor of previous code may be difficult. Code evolves over time with extra options, new or altering dependencies, or possibly a purpose of efficiency enhancements. When tackling a giant refactor, what are the issues it’s best to deal with and what efficiency enhancements are you able to anticipate?

I’ve been constructing Shopify themes for the higher a part of a decade. Once I labored in-house at Shopify in 2013, themes had 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 lots of fashionable JavaScript we couldn’t make the most of with out typically sizable polyfills.

Eight years later, in 2021, themes are infinitely extra complicated as a result of Shopify has launched a ton of latest options (to associate 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 previous that it has previous IE polyfills or IE10 CSS hacks. Our themes had fairly good pace scores for a way a lot they supplied, however they had been undoubtedly bloated.

Our Aim 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 had 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 a protracted course of. Fortunately, Tobias Ahlin has a unbelievable information on a number of the quick conversions away from jQuery. Whereas going by these modifications, it was the proper time to rethink some extra primary points like how my JS was structured and the way parts had been initialized.

Take away jQuery

Writing Vanilla JS all the time appeared like a pipe dream. We needed to help previous 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 number of arguments about this, reminiscent of its bundle measurement isn’t that unhealthy in comparison with a framework like React. Nicely, 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 number of causes:

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

Our method was to remark out every module we had, take away jQuery, and slowly add in every module or operate separately whereas it was rewritten. We began with the best file, one with a number of features and some selectors. Good and simple, 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 obtained to the complicated ones like refactoring all the potential options related to a product and its add-to-cart type (I counted, it’s 24 distinctive issues). Ultimately, we obtained the product JS from 1600 traces of code to 1000. Alongside the best way, 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 historical code was a multitude — we wanted to arrange 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 number 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 parts across the web page nevertheless they need. Which means, because the developer, you don’t know the place the factor is, whether or not it exists, or what number of exist.

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

theme.isElementVisible = operate($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 factor is completely 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)

Despite the fact that these scroll occasions had been throttled, there was lots of math being executed by the browser on a regular basis. It by no means actually felt too sluggish, however it did take up a spot within the call stack which impacted different JS competing for precedence. I want we had executed extra efficiency analysis on this replace particularly as a result of I feel it’s liable for most of the enhancements in Time to interactive and Complete blocking time that you just’ll see beneath.

In comes the Intersection Observer API. Now that IE11 help wasn’t required, I used to be so completely satisfied to have the ability to absolutely make the most of this. In brief, it’s an asynchronous means of realizing 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:

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

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

This units up an observer for that factor, after which removes the observer as soon as it’s seen. It’s all the time good to wash up after your self even if you happen to suppose there may not be a lot influence with out it. If there’s a callback, we run it and our module is able to go.

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

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


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

Layzloading Photos 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 factor. Whereas the plugins are fairly small, it’s another factor that’s simply eliminated with pure CSS.

Utilizing object-fit in CSS meant that we might place a picture similar to a background picture, however as an <img> factor and get all the advantages of regular lazy-loading with out further 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 should load in lazysizes as a fallback when the native method isn’t supported, however it means eradicating a complete 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 parts, altering a module’s arguments for desktop vs cellular, or just to point out/conceal parts which you could’t with CSS.

As a substitute of counting on one other bundle, as soon as once more we will go together with a local answer in matchMedia.

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

matchMedia(question).addListener(operate(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 pay attention for breakpoint modifications and alter a useful variable that’s used elsewhere and set off a customized occasion that particular modules can pay attention for.

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

Looking down duplicate code

As I discussed in the beginning, we had slowly constructed options into our themes for years. It didn’t take lengthy for some parts to be constructed out that had been sort 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 similar 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 lots of our modules to be standalone helpers. YouTube turned its personal technique that every one sections from all of our themes might use. It meant refactoring by breaking it down into probably the most primary 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 many others)
  • Play/pause when not in view
  • Deal with iOS low energy mode when autoplay not supported

My method was to do that all on paper earlier than coding, which is one thing that all the time helps me type out what’s integral to the module I’m constructing vs what’s customized by the guardian that’s initializing it — a division of labor if you’ll.

Now our three themes that initialize YouTube movies a complete of 9 other ways use a single file. That’s a giant code complexity win for us, and makes any future updates a lot simpler for me and different builders that may contact the code. By utilizing this identical method 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 imagine the method is. Enthusiastic about simplicity and avoiding duplication will all the time profit your undertaking.

We did the identical for slideshow modules (picture slideshows, testimonials, product web page photos, announcement bars), drawers and modals (cellular menus, cart drawers, e-newsletter popups), and lots of extra. One module has one objective and can share again to the guardian 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 executed blindly with the belief that much less JS, smarter initializing, and extra fashionable approaches would end in 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 residence web page speeds (with heavy parts like a number of movies, featured merchandise, slideshows with giant photos)
Desktop residence web page Earlier than After Change
Lighthouse rating 57 76 +33
Complete blocking time 310ms 50ms -83.8%
Time to interactive 2.4s 2.0s -16%
Largest contentful paint 3.8s 2.6s -31.5%
Cell product web page Earlier than After Change
Lighthouse rating 26 65 +150%
Complete 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 cellular residence web page speeds
Desktop residence web page Earlier than After Change
Lighthouse rating 58 81 +39.6%
Complete blocking time 470ms 290ms -38%
Time to interactive 6.1s 5.6s -8%
Largest contentful paint 6s 2.9s -51.6%
  • 30% quicker cellular residence web page and product web page speeds
Cell product web page Earlier than After Change
Lighthouse rating 32 45 +40.6%
Complete blocking time 1490ms 780ms -47.6%
Time to interactive 10.1s 8.3s -17.8%
Largest contentful paint 10.4s 8.6s -17.3%

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

  • Shopify has lots of overhead: characteristic detection, monitoring, and cost buttons (Apple Pay, Google Pay, ShopPay). In case you’re on a product web page with dynamic cost buttons you may be taking a look at about 187kb of Shopify scripts vs. 24.5kb theme information. Most websites may have Google Analytics, and possibly 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 unhealthy information is that there’s nonetheless lots of JavaScript loading on these pages which are out of the theme’s management and trigger some flags on Lighthouse scores.

(Large preview)
  • Apps are an enormous bottleneck and retailer homeowners, typically, do not know. We routinely see retailers 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 ideal case study on apps and their effect on performance.

We’re nonetheless within the technique of ending these updates to our third theme, Streamline. Streamline additionally has another efficiency options in-built that we’re exploring including to our different themes, reminiscent of loadCSS by Filament Group to stop 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 pleased with all of this progress, it’s not the tip. Efficiency will proceed to be a dominant a part of our builds and we gained’t cease searching for extra methods to simplify code.

What’s Subsequent?

Efficiency is an ongoing problem, one we’re excited to maintain pushing on. Just a few issues on our listing are:

Sources For Shopify Builders

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

Smashing Editorial
(vf, il)

Source link