Magento 2 JavaScript Bundling: What Actually Makes It Faster
A Luma storefront loads a lot of JavaScript. On a product page it is normal to see well over a hundred RequireJS modules requested one by one, each a small file. Bundling promises to fix this by grouping them into fewer, larger files. The built in option often makes things worse, and the options that help need more setup.
This article covers what Magento 2.4.8 offers out of the box, why the default bundling is a poor fit for most stores, what Magepack does differently, and how to measure whether any of it helps.
Why Luma loads so many files
Luma uses RequireJS, which loads modules on demand. A template declares a component through data-mage-init or text/x-magento-init, RequireJS fetches it, reads its dependencies, fetches those, and so on. Each level of the dependency tree adds a round trip before the next files can be requested.
With HTTP/2, many small requests are cheaper than they used to be, because they share one connection. The round trips caused by the dependency chain remain, though. The browser cannot request a module it does not yet know it needs.
The built in options
All of these live under Stores, Configuration, Advanced, Developer, and that section is only visible in developer mode. In production mode, set them from the CLI:
bin/magento config:set dev/js/minify_files 1
bin/magento config:set dev/js/merge_files 0
bin/magento config:set dev/js/enable_js_bundling 0
bin/magento config:set dev/js/move_script_to_bottom 1
bin/magento config:set dev/css/minify_files 1
bin/magento config:set dev/template/minify_html 1
The values above are a reasonable starting point for many Luma stores, not a universal answer. Each option deserves a closer look.
Minification
dev/js/minify_files serves minified versions of JavaScript files. It reduces transfer size with no real downside once your code works. Keep it enabled in production.
Merging
dev/js/merge_files merges the scripts declared in layout XML head sections into one file. It does not touch RequireJS modules, which are the bulk of the JavaScript, so the gain is small. On HTTP/2 it is usually not worth enabling.
Built in bundling
dev/js/enable_js_bundling groups RequireJS modules into bundle files during static content deploy. The problem is how it groups them. It packs the modules of the whole theme into bundles of a configured size, with no idea which page needs which module. The browser then downloads every bundle on every page, including code for the checkout, the customer account and widgets the visitor never sees.
The bundle size and exclusions are set in the theme's etc/view.xml:
<vars module="Js_Bundle">
<var name="bundle_size">1MB</var>
</vars>
<exclude>
<item type="file">Lib::jquery/jquery.min.js</item>
<item type="directory">Lib::tiny_mce</item>
</exclude>
Excluding large libraries you only need on a few pages helps, but it does not fix the underlying design. On most stores I would leave built in bundling off and test it only as a measured experiment.
Move scripts to bottom
dev/js/move_script_to_bottom moves inline and external script tags to the end of the body. It can help render times, but it breaks inline scripts that expect a library to be loaded before them. Test the full checkout and any third party widgets after enabling it.
Magepack: bundling by page type
Magepack is an open source tool by Creativestyle that takes a different approach. It loads real pages of your store in a headless browser, records which RequireJS modules each page type uses, and builds bundles from that:
- a common bundle with modules every page type uses
- a bundle for CMS pages
- a bundle for category pages
- a bundle for product pages
- a bundle for the checkout
Each page then loads the common bundle plus its own, instead of a long chain of individual files. It needs a companion Magento module that adds the bundles to the right pages. The flow is:
npm install -g magepack
magepack generate \
--cms-url="https://shop.example.test/" \
--category-url="https://shop.example.test/women/tops.html" \
--product-url="https://shop.example.test/some-product.html"
magepack bundle
generate writes a magepack.config.js file that you commit to the repository. bundle runs after static content deploy on every build, and creates the bundle files inside pub/static for each theme and locale. Check the project's README for the exact module name and Magento version support before installing, because community tools change faster than core.
Things to watch:
- The generate step needs real URLs with typical content. If your product page has optional widgets on only some products, pick a product that has them.
- Regenerate the config after adding modules that change frontend JavaScript. Otherwise new modules are loaded the old way, which still works but loses the gain.
- Keep the bundle step in the deploy pipeline. A deploy that skips it leaves the store without bundles, which still works, just slower.
Advanced bundling with r.js
Adobe documents an advanced bundling approach using the RequireJS optimizer, r.js, with a hand written configuration per page type. It is the same idea as Magepack but you maintain the module lists yourself. It gives full control and costs a lot of maintenance. For most teams Magepack is the practical version of it.
How to measure
Bundling changes are easy to get wrong in both directions, so measure before and after on the same pages, with the cache warm:
- Pick a home page, a category page, a product page and the cart.
- Run Lighthouse in Chrome DevTools or WebPageTest with mobile throttling, several runs each, and note the median.
- Look at Total Blocking Time and the time until the page becomes interactive, not only the score.
- Open the Coverage tab in DevTools to see how much of the loaded JavaScript is unused on each page.
- After release, watch real user data in the Search Console Core Web Vitals report for a few weeks.
If Coverage shows that most of the JavaScript is unused, bundling has a ceiling. The Core Web Vitals guide covers the other levers, such as deferring third party scripts and reducing what runs on load.
Deploy notes
None of this works in developer mode, where files are served individually so you can debug them. Bundling, minification and merging take effect in production mode after bin/magento setup:static-content:deploy. Make sure the build server and the production server use the same configuration values, or store them in app/etc/config.php by setting them with bin/magento config:set --lock-config. The production mode deploy article covers the build steps.
When to stop optimising Luma JavaScript
Bundling reduces the cost of loading Luma's JavaScript. It does not reduce the amount of JavaScript that has to run, and on mid range phones execution time is often the bigger problem. If you have already enabled minification, set up Magepack and deferred third party tags, and the store is still slow on mobile, the next step is a frontend with less JavaScript. The Hyvä vs Luma comparison looks at that decision.
FAQ
Should I enable Magento's built in JS bundling?
Usually not. It bundles all theme modules without knowing which page needs them, so every page downloads code it never uses. Measure it if you want, but per page bundling with a tool like Magepack is the better option.
Does JS merging help on HTTP/2?
Very little. It only merges scripts declared in layout head sections, not RequireJS modules, and HTTP/2 already reduces the cost of separate requests.
Why do my bundling settings not change anything?
The store is in developer mode, or static content was not deployed again after the change. These settings only apply to files generated by setup:static-content:deploy in production mode.
Is Magepack safe for the checkout?
It can bundle checkout modules, but test the full checkout with every payment method afterwards. If anything breaks, you can exclude the checkout from bundling and keep the gains on catalog pages.
I am available for freelance Magento and Adobe Commerce work, including frontend performance audits on Luma stores. If you need help with this, get in touch.
Related articles
Magento 2 Customer Data Sections: How Private Content Works
How Magento 2 customer data sections keep cached pages personal: sections.xml, section sources, customer-data.js, invalidation and the mistakes that break FPC.
Magento 2 RequireJS Mixins: Extend Core JS Without Overrides
How to write RequireJS mixins in Magento 2 for UI components, jQuery widgets and function modules, plus how to debug them and when a map override is better.
Writing Alpine.js Components in Hyvä: Patterns That Scale
How to write Alpine.js components on Hyvä: passing PHP data, view models, customer data events, form keys, component events, CSP and keeping templates clean.
Luma to Hyvä Migration Plan: Audit, Phases and Launch
Luma to Hyvä migration plan for Magento 2: extension and theme audit, checkout decision, page by page rebuild, SEO parity, testing, launch and rollback.