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 perfect consumer 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 crew improved the efficiency and group of their Shopify themes, and improved maintainability alongside the way in which.

The dreaded refactor of previous code will be difficult. Code evolves over time with extra options, new or altering dependencies, or perhaps a purpose of efficiency enhancements. When tackling a giant refactor, what are the issues you must concentrate on 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 have been pretty easy by way of code complexity. The toughest half was that Shopify required themes to help IE8, and up till late 2020, IE11. That meant there was a whole lot of fashionable 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 recent options (to associate with our in-house concepts at Archetype Themes). The issue is that constructing new performant options will solely go up to now 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 provided, however they have 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 have been approach too small for such a big package deal
  • 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 implausible information on among the quick conversions away from jQuery. Whereas going by these adjustments, it was the right time to rethink some extra primary points like how my JS was structured and the way components have 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 lot of arguments about this, comparable to its package deal measurement isn’t that unhealthy in comparison with a framework like React. Effectively, jQuery isn’t a framework like React so it’s a little bit of a non-starter comparability. jQuery is a approach 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 just a few causes:

I’m a kind of builders who have been caught up to now. I knew jQuery inside and outside and will make it pull off practically something I attempted. Was it good? No, in fact not. However whenever 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 protected 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 strategy was to remark out every module we had, take away jQuery, and slowly add in every module or perform one after the other whereas it was rewritten. We began with the only file, one with just a few capabilities 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 once we obtained 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 obtained 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 approach of writing code than jQuery. We additionally realized some historic code was a large number — we wanted to prepare the JS to be extra modular and take away duplicate code (extra on that under). However earlier than that, we needed to play with among 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 nonetheless they need. Meaning, because the developer, you don’t know the place the aspect 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 aspect is completely hidden
  if ($el[0].offsetParent === null) {
    return false;

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

Though these scroll occasions have been throttled, there was a whole lot of math being achieved 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 achieved 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 just’ll see under.

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

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

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

This units up an observer for that aspect, after which removes the observer as soon as it’s seen. It’s all the time good to scrub up after your self even in case you assume 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’ll be able to go a threshold to initialize the aspect earlier than it’s on the display screen too, which will be helpful if you wish to preload one thing like Google’s Map API barely earlier than the aspect is seen so it’s prepared when it’s.

Layzloading Pictures 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 aspect. Whereas the plugins are fairly small, it’s yet one more 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> aspect and get all the advantages of regular lazy-loading with out further JS. The actual 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 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/cover components you could’t with CSS.

As a substitute 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 screen 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 only a few 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 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 type 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 otherwise 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 understand you’re rising as a developer.

We took this time to consolidate a lot 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 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 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 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 crew and our multi-project setup and may not be helpful to your tasks precisely, however I imagine the method is. Serious about simplicity and avoiding duplication will all the time profit your challenge.

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 dad or mum solely what’s required. This meant much less code shipped, and cleaner code to develop with.

Efficiency Stats

Lastly, the great things. Was this all price it? Most of this was achieved blindly with the belief that much less JS, smarter initializing, and extra fashionable approaches would lead to 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 most important 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 photos)
Desktop house 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%
Cell 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% sooner cellular house web page speeds
Desktop house 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% sooner cellular house web page and product web page speeds
Cell 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%

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

  • Shopify has a whole lot of overhead: function 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 will be taking a look at about 187kb of Shopify scripts vs. 24.5kb theme recordsdata. Most websites may have Google Analytics, and perhaps a Fb Pixel or different monitoring scripts loaded on prime 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 a whole lot 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, typically, do not 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 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, comparable to loadCSS by Filament Group to forestall the CSS from being a render-blocking useful resource.

These numbers aren’t insignificant. It’s extensively 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 in search of 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:

Assets 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