Update from Nov 19, 2020: Please note that due to a Google PageSpeed update the guide found here still works, but no longer has the desired effect for Lighthouse. If you are looking for an alternative to improve the performance of your shop, Hyvä Themes might be of interest to you.

Quite recently we’ve been struggling with front end performance on a few Magento 2 projects at integer_net. There are several tools and solutions out there that help improving the JavaScript performance, but it didn’t quite work for us.

Results varied between projects and we had many setbacks where something that performed really well before suddenly gave us very bad Google PageSpeed results due to an update to the algorithm Google uses to calculate the scores.

We learned so much on the topic that we thought it would be helpful to document and share our findings. This ought to save you many hours of research and trial & error.

In this guide, you’ll find some different approaches, the pros and cons of each approach and complete how-to’s to get started on your own projects.

Do give us a shout out and let us know what solution worked best for you since we’d really like to validate our findings. Comment on the bottom of this page or ask me on Twitter @willemwigman.

This guide consists of four parts. We’ll first share our experience and results showing benchmarks of different techniques currently available. Then we have three in-depth parts where we show you how to apply these techniques and measure the results. This way you’ll have all the information to get started and find the best suited approach for your Magento 2 project.

You may choose to skip some parts, but the guide is originally meant to be read in chronological order.

 

Table of Contents

Part One

You are here

About Lighthouse Audits
Benchmarks
How JavaScript influences the performance

The Basis – default Magento 2 setup
Patches
Default Magento Bundling

Baler
Magento 2 DevTools browser extension
DevTools bundling

integer_net Bundling
A real-life example
Conclusions

 

About Lighthouse Audits

In this guide we’ll be talking a lot about Performance Audit scores. Let’s start by explaining what scores we are talking about.

There are many ways to run a performance audit. When working on a local environment, the Audit tab in Chrome inspector is the easiest one.

Alternatively, you can run Lighthouse from the command line:
sudo npm install -g lighthouse
lighthouse https://magento2.test/ --chrome-flags="--ignore-certificate-errors" --view --only-categories=performance

The outcome/score of the command line version is actually a few points higher, probably because the headless Chrome browser it utilizes is faster. But the downside is that you can’t easily navigate to a populated cart and checkout.

Another option is to run an analysis on Google PageSpeed Insights. This uses historical data from actual users who visited your shop. This is helpful to see how (badly) your site currently performs, but not very helpful if you want to measure the improvements you just made.
Also, the outcome/score is usually considerably lower than what Lighthouse gives you.
Personally, I find Lighthouse Audits more motivating.

Now here’s the catch: The Lighthouse audit depends on the performance of your PC. Not just how fast your dev environment serves the requested Magento shop, but actually generating the report in the browser depends on the available CPU power.
I even experience an immense drop in score when connecting my laptop to my 4K external screen.

So whenever running benchmarks, make sure your PC is not occupied with other CPU heavy tasks.

 

Benchmarks

– Not to be confused with Ben Marks

I ran Lighthouse Audits on several Magento configurations to offer an overview of the effectiveness of available methods. The results are in the table below.

The tests were conducted on Magento 2.3.3 with sample data, running in production mode, using gzip, HTTP/2 and Varnish.
We use an in-house docker setup based on the one from Mark Shust. If you are looking for a way to add Varnish, you can get inspiration from my (outdated, but working) fork.

The PC I used is an IBM Thinkpad T580, running Ubuntu and configured with an 8-core i7 CPU at 1.80 GHz.

I ran the Audit for each configuration as follows: I used the Audits tab from Chrome, operating in Incognito Mode (so no plugins can interfere). Then I fired up the homepage of the shop, and refresh the page a few times so that the cache is warm.
Then I run the Performance Audit, in mobile mode, with throttling. I run the audit three times and take the best result out of those three. I always pause between actions to wait for the fans of my PC turn off, indicating the CPU is back to a normal level.

I am not so much interested in the desktop results since it’s not so strict and performance is MUCH less of an issue than on mobile. Get a good score on mobile, and desktop will be perfect.

 

The configurations that I have tested are:

Default: JS minification enabled, no bundling
Bundling: JS minification and default bundling enabled
Baler: JS minification and bundling disabled, baler-bundling and terser minification via command line
Devtools bundling: JS minification and bundling disabled. Advanced bundling and minification via r.js using configuration generated by M2-Devtools plugin
Our ‘integer_net’ config: JS minification and bundling disabled. Advanced bundling via r.js using configuration generated by M2-Devtools plugin, with some adjustments. Minification through Terser.js

We’ll discuss each of these configurations in detail later on.

 

Benchmark Results

Default Bundling Baler Devtools integer_net
Homepage 84 5 88 86 91
Category 85 5 89 87 90
Product 81 4 85 83 88
Cart 71 4 74 77 82
Checkout 47 4 50 71 73

What conclusions can we draw from these results?

  1. Don’t use the default Magento bundler. In fact, I think it should be removed from Magento core. There is no thinkable situation where default bundling improves performance.
  2. At first glance, the whole topic of bundling doesn’t seem that important. Default Magento isn’t too bad, right?
  3. Upon closer inspection, we see some more notable differences, e.g. at the cart, but especially at the checkout. Why is that?

 

Why bundle at all?

Magento 2 uses RequireJS to load JavaScript modules asynchronously, and only when needed.
This means that when a page is rendered by your browser, it doesn’t know too much about what requests are coming up. RequireJs picks up require() and x-magento-init tags from the body and only then looks up the modules from requirejs-config.

This is why it takes a while before you see the mini cart of a Magento shop showing the quantity, which is a good indicator of when your shop’s JavaScrip finally finished loading.
The mini cart depends on section-data which has to be loaded from localStorage and there are a few checks that need to be done before the customer and cart from section-data are initialized.
If there is no valid section-data available, it will even fetch it from the server before showing the cart.

This mechanism is great to reduce the amount of bits and bytes that need to be sent to a visitor, since your browser is only requesting files that are needed, and it’s flexible in a way that adding a widget in the Magento admin that needs certain JavaScript files means these JavaScript files are automatically added to the page in the frontend.
It also improves extensibility and modularity, so it’s not a bad thing in these senses.
However, it’s not a great experience for a visitor to look at a loading spinner while your checkout is loading or when your page repaints several times as widgets and sliders are initialized.

Advanced Bundling improves this customer experience greatly. If we ignore the Google performance score and just look at the experience for the customer, having these bundles loaded as soon as possible means the page is built much quicker.

 

Doesn’t HTTP/2 solve this?

Unfortunately, no. Not in 2019. Because your browser doesn’t know what files are going to be required when parsing the body of the page, it has no idea which JavaScript files to queue up so that they can be loaded in a single request through HTTP/2’s chaining mechanism.

A way to fix this would be pre-connecting these files from the head, but that requires you to know what files will be required on that particular page. However, if this is possible with bundling, it surely is possible to build this for pre-connecting too.
In fact, Yireo has a module that adds a pre-load to the page request header and Andrew Levine from Magento is supposedly working on an implementation for Baler that should do something similar.

 

But still, those Audit results don’t look that far apart

True, when you look at the default Magento shop with sample data, the Audit score only gets worse when approaching the checkout.
However, the sample data doesn’t represent the average shops that we’ve seen. In reality, your client will want at least a slideshow on the homepage. And three sliders with featured products. Plus a Masonry Grid with a few images. You probably get the idea.
And since you’re reading this article, you probably face the same issues we did. The content is much ‘richer’ in reality than with sample data. There will be more images, more JavaScript, and – if you’re really unlucky – your client even added a 5mb JavaScript-heavy chatbox through Google Tag Manager.

We typically see a Lighthouse performance score that’s much closer to 50-60 throughout the site. That’s with bundling, tested in mobile mode with throttling.

 

How JavaScript influences the performance

Nowadays, Google pays a lot of attention to the time it takes before a mobile device can render your site responsively. This is measured by ‘time to interactive’ and ‘max potential first input delay’. There’s a balance between getting the quickest first render of your page and having all JavaScript loaded as soon as possible.

 

Analysis of the Results

Default Magento does a good job at rendering the initial page, but it takes more time before all JavaScript is loaded and the device finishes loading the entire page (“final render”), especially on pages with a lot of JavaScript.

Default Magento bundling does a terrible job at basically everything because its generated bundles contain all possibly needed JavaScript on any page and it forces these bundles down the throat of your browser, blocking rendering of the page.
It does a bad job at rendering the initial page, and it does a bad job at finishing quickly.

Baler does an excellent job at creating a small async bundle that’s optimized for quick initial render and does a decent job at quickly finishing rendering the entire page. But the big caveat is the same as with default Magento config, the more JavaScript you get, the worse the ‘long tail’ gets. We didn’t get great results with this method – yet – since most of the JavaScript is still rendered on request.
As mentioned above though, Andrew is still working on this part. And we’re pretty sure somewhere in 2020 this will become the best and simplest solution for bundling.

Please note that you can’t use default Magento minification with Baler, and it currently only minifies the two bundles it creates. So, we’ve given Baler a fair chance by running Terser on all non-bundled JavaScript files which increased the score by 5-10 points.

Devtools Bundling doesn’t perform well on initial render since the bundles are injected into the head of the page and loaded synchronously. This means rendering of the page is halted until the bundles are loaded. The gains from having bundles are lost by the time it takes to load the bundles.

integer_net Bundling seems to get a good balance between initial render and final render. The difference doesn’t look that big in our benchmark, but in real-life projects, we got the best results by far. We’ll share our workflow further down this article.

 

Lighthouse Audit Performance Score breakdown per configuration

 

Homepage

Default Bundling Baler Devtools integer_net
First contentful Paint 1.2 s 12.3 s 1.3 s 2.8 s 1.5 s
First Meaningful Paint 1.2 s 12.3 s 1.3 s 2.8 s 1.5 s
Speed Index 2.4 s 12.3 s 1.8 s 2.8 s 2.1 s
First CPU Idle 4.7 s 13.4 s 4.1 s 3.7 s 3.9 s
Time to Interactive 6.0 s 14.5 s 5.4 s 4.7 s 4.9 s
Max Potential First Input Delay 230 ms 480 ms 220 ms 240 ms 210 ms
Render-blocking resources 260 ms 11410 ms 320 ms 1860 ms 560 ms
Total Audit Result 84 5 88 86 91

 

Checkout

Default Bundling Baler Devtools integer_net
First contentful Paint 1.4 s 12.4 s 1.4 s 3.0 s 1.6 s
First Meaningful Paint 1.8 s 12.6 s 2.5 s 3.1 s 2.3 s
Speed Index 8.2 s 12.4 s 7.6 s 4.8 s 4.7 s
First CPU Idle 8.3 s 12.6 s 7.4 s 5.0 s 5.7 s
Time to Interactive 9.1 s 16.1 s 8.7 s 5.5 s 6.2 s
Max Potential First Input Delay 600 ms 250 ms 590 ms 200 ms 550 ms
Render-blocking resources 490 ms 11480 ms 460 ms 2030 ms 690 ms
Total Audit Result 47 4 50 71 73

These numbers show that our approach is not necessarily the best in each individual category, but it’s the balance between the metrics that lead to the highest Total Audit Results with our approach.

This also means that the scores really depend on the type of page and the amount of scripting that happens on the page. We’ve seen scores varying between our projects, but overall our approach always scores best.

So, how should you approach bundling for your project? We’ll admit that currently, our approach is the one that takes the most effort of the approaches presented here, but we’ll give you all the steps and try to make it as simple as possible.

However, we’d advise you to start with default configuration as a base reference, then try Baler and then finally try our approach. Then after comparing your results, make a decision what fits best for your project.
You might not be obsessed with getting a green circle with a 90+ score on each page, and that would be perfectly healthy if the extra effort is not worth it to you, or your client.

We’ll continue to explain how to implement and test.

You almost finished reading the first part.
Now would be a good time to skip to Part Two, Three or Four.

How do you wish to proceed?

Part One

You are here

About Lighthouse Audits
Benchmarks
How JavaScript influences the performance

The Basis – default Magento 2 setup
Patches
Default Magento Bundling

Baler
Magento 2 DevTools browser extension
DevTools bundling

integer_net Bundling
A real-life example
Conclusions

Willem Wigman

Author: Willem Wigman

Willem Wigman is Certified Magento 2 developer at integer_net. His focus lies in front- and backend development and ReactJS.

Willem started as a freelance developer in 2007, works with Magento since 2011 and ran his own Magento Agency for a couple of years.

More Information · Twitter · GitHub