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 usually grow to be a bottleneck. On this article, Carson Shold discusses how his staff improved the efficiency and group of their Shopify themes, and improved maintainability alongside the best way.

The dreaded refactor of previous code might be difficult. Code evolves over time with extra options, new or altering dependencies, or perhaps a purpose of efficiency enhancements. When tackling an enormous refactor, what are the issues it is best to 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. 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 assist IE8, and up till late 2020, IE11. That meant there was plenty 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 recent options (to go together with our in-house concepts at Archetype Themes). The issue is that constructing new performant options will solely go to date when a few of your codebase is so previous that it has previous IE polyfills or IE10 CSS hacks. Our themes had fairly good velocity scores for a way a lot they supplied, however they had been undoubtedly bloated.

Our Purpose Was Easy

Higher efficiency throughout the board. Quicker 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 package deal
  • Standardizing code shared between themes (take away duplication)

Shifting away from jQuery was a blessing, however a protracted course of. Fortunately, Tobias Ahlin has a improbable information on a number of the quick conversions away from jQuery. Whereas going via these adjustments, it was the right time to rethink some extra primary points like how my JS was structured and the way components had been initialized.

Take away jQuery

Writing Vanilla JS all the time appeared like a pipe dream. We needed to assist previous IE, so it was simply really easy to disregard any try at eradicating it. Then IE 11 assist 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, equivalent to its package deal measurement isn’t that unhealthy 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 wished to take away it for just a few causes:

I’m a kind of builders who had been caught previously. I knew jQuery inside and outside and will make it pull off practically something I attempted. Was it good? No, after all not. However once you take 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 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 one by one whereas it was rewritten. We began with the best file, one with just a few 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 simple fixes from the early recordsdata after we received to the complicated ones like refactoring the entire potential options related to a product and its add-to-cart type (I counted, it’s 24 distinctive issues). Ultimately, we received 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 historic code was a large number — we wanted to arrange the JS to be extra modular and take away duplicate code (extra on that under). However earlier than that, we wished to play with a number of the enjoyable JS we’d solely utilized in different initiatives.

Intersection Observer API

Shopify themes are highly effective in that they let retailers transfer components across the web page nonetheless they need. Meaning, 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 components, we had been utilizing scroll occasions that repeatedly 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 factor is solely 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 plenty of math being performed 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 performed extra efficiency analysis on this replace particularly as a result of I believe it’s answerable for most of the enhancements in Time to interactive and Complete blocking time that you just’ll see under.

In comes the Intersection Observer API. Now that IE11 assist wasn’t required, I used to be so pleased to have the ability to absolutely 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:

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

All the JS required for the factor will probably 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 scrub up after your self even when you 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 = 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 may go a threshold to initialize the factor earlier than it’s on the display too, which might be helpful 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 Photographs And object-fit

We use lazysizes for lazy-loading our pictures. It has some useful plugins for additionally loading background pictures, however requires much more markup in your factor. 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 identical to a background picture, however as an <img> factor and get all the advantages of regular lazy-loading with out additional JS. The actual profit in that is we’re one step nearer to utilizing native browser lazy-loading (which doesn’t assist background pictures). We’ll nonetheless should 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

Up 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 cellular, or just to indicate/conceal components that you could’t with CSS.

As an alternative of counting on one other package deal, as soon as once more we will go together 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 adjustments 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

Searching down duplicate code

As I discussed initially, we had slowly constructed options into our themes for years. It didn’t take lengthy for some components 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 a different way thrice and had practically equivalent 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 recognize you’re rising as a developer.

We took this time to consolidate lots of our modules to be standalone helpers. YouTube turned 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 primary components:

  • 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 strategy was to do that all on paper earlier than coding, which is one thing that all the time 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 other 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 similar strategy for different modules whereas changing to Vanilla JS, it allowed us to maneuver practically half of every theme’s JS to a single shared module throughout all of them.

That is one thing that was invaluable to our staff and our multi-project setup and may not be helpful to your initiatives precisely, however I imagine the method is. Excited about simplicity and avoiding duplication will all the time profit your venture.

We did the identical for slideshow modules (picture slideshows, testimonials, product web page pictures, announcement bars), drawers and modals (cellular menus, cart drawers, publication popups), and lots 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 value it? Most of this was performed blindly with the belief that much less JS, smarter initializing, and extra fashionable approaches would end in sooner 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 largest room for enchancment.

  • 52% much less JS shipped
  • Desktop house web page speeds (with heavy components like a number of movies, featured merchandise, slideshows with massive pictures)
Desktop house 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%
Cellular 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% sooner cellular house web page speeds
Desktop house 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% sooner cellular house web page and product web page speeds
Cellular 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%

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

  • Shopify has plenty of overhead: characteristic detection, monitoring, and cost buttons (Apple Pay, Google Pay, ShopPay). If you happen to’re on a product web page with dynamic cost buttons you might be taking a look at about 187kb of Shopify scripts vs. 24.5kb theme recordsdata. Most websites can 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 unhealthy information is that there’s nonetheless plenty of JavaScript loading on these pages which can 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 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 amazing case study on apps and their effect on performance.

We’re nonetheless within the means 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, equivalent to 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 proud of all of this progress, it’s not the tip. Efficiency will proceed to be a dominant a part of our builds and we received’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 record are:

Sources For Shopify Builders

If you happen to’re constructing on Shopify, or wish to get began, listed here are some useful assets for you:

Smashing Editorial
(vf, il)

Source link