Xumulus Blog

Kill the Loader – How to Improve Your Magento 2 Product Page Load Time

This post resulted in the Issue being fixed in Magento 2.3.2!  So this is no longer an issue unless your theme is overriding with old Magento code (And I would not be surprised if they are)

———————————————————— Original Post Below

I knew from the moment I laid eyes on it I hated it.  The spinny thing just sitting there making the page look oh so damn slow. Yuck!!  Surely when the images are loaded in the cache and I see a page refresh it will go away, right?  Nope. still there and still as clunky looking as ever. Oh my, how did it take me this long to try and kill it?  This is why I had to fix the Magento 2 image loader speed. This is what it looks like in the Magento 2 Demo store.

See it live here.

Now there is a lot of motion on this page and most of it does not do much good. The most offending is the image loading, instead of loading the first image it loads a spinner.  Not a great user experience if you ask me. Now let’s take a look at what it looks like if we replace the loading image with the first one in the list.

Much better!  I still think we can improve this more by trying to remove almost all motion from the page, like the breadcrumb ajax load. But we can deal with that at another time. Now there are still a few issues with the Fotorama gallery.  It flashes when the Fotorama js applies the logic that makes the gallery come to life. We will try and work on that later.

The Core Problem

I went looking to see if there was any solution anyone else came up with and there were none that looked interesting. We found sites that have fixed it themselves.  I also came across this in the Magento 2 GitHub issue repository. There seemed to be some misunderstanding in the thread of the core issue here. It’s basically built like this:

1. The HTML is built with a spinner gif as the primary image in the product image location in gallery.phtml.
<div class="gallery-placeholder _block-content-loading" data-gallery-role="gallery-placeholder">
    <div data-role="loader" class="loading-mask">
        <div class="loader">
            <img src="<?= /* @escapeNotVerified */ $block->getViewFileUrl('images/loader-1.gif') ?>"
                 alt="<?= /* @escapeNotVerified */ __('Loading...') ?>">
2. The page is loaded by the browser and loads the spinner in the page in place of a product image.
3. Javascript then fires after the page is loaded (and is competing with lots of other scripts loading)

This loads the data-gallery object which then takes setting and image data from Magento and builds the Fotorama gallery.  This is where a lot of the problems are since the HTML above is then replaced by all the gallery HTML, you can get motion, resizing and such. It also makes styling it a bit tricky as the HTML is generated in Javascript. The gallery data should be loaded into HTML not Javascript.

4. Finally when all that is complete the spinner is removed and the real images are shown (maybe after 3-8 seconds)

With other scripts competing on the page, it could take a while after the page is loaded to fully complete. Worse yet is that even if the images are cached, it sill needs to process the page build and js before the spinner goes away since it is basically loaded as the core image in step 1.

Perception and Performance Are the Same Things

Many merchants, hosting companies, and agencies spend lots of time optimizing theirs or their client’s websites for speed. The issue is that you can still have a time to first byte (TTFB) that is stellar, but the product page will load very slow because all the JavaScript has to process first. So even if you have a 50ms TTFB you are going to have 4 seconds for the spinner to disappear, and it’s going to look and feel slow. Let’s look at Sole Society’s Magento 2 site, it has a stellar 76MS TTFB and loads the HTML in just 120MS.  Yet the image loader does not go away for 4 seconds. and there is zero improvement from the browser cache. Yuk. Now here is Chanel Magento 2 site they opted for a huge mega resolution image. Its TTFB is about 60MS does load in about 2 seconds for me (results may vary) but it feels even faster. If you refresh the page and the image is cached and loads instantly. Both of these sites share a very similar TTFB but the perception of speed for me was cleary with Chanel.

The Solution

The solution is to generate as much of the HTML/CSS on the server side, then when the page first builds this is loaded by the browser and then rendered almost immediately.  If you ask me this is how it should be built in the core.  And, I might add the practice around the internet of putting image loaders up in sliders, gallery and such needs to stop. It’s way out of control with WordPress plugins and the like. In this day and age, we simply need to put the intended images up and if your optimizing mobile you should just put up a smaller one. We created a base extension that does just that to the standard Magento theme.  The problem is though that many theme vendors and agencies are going to customize the gallery.phtml and so you may still have to do some development work to override that customization. You can get our fast image load Magento 2 extension on GitHub that greatly improves the experience.  Though there are some things that make it a challenge, this will work int he Magento base theme Luma but may not in your solution, any good developer can probably figure out how to do the same in your site. (or email us) I think this whole thing could be hugely improved in the core. We would simply need to engineer the gallery to load an image of choice first, the improve the transition when the gallery is loaded to avoid a flash. Or remove Fotorama (Magento’s image gallery plugin) altogether, with some simple HTML and a touch of JavaScript.  As a side note and the subject of another blog, we need to get the amount of JavaScrip in Magento way down, most of it is not needed. We would love some feedback on your thoughts for a longer term fix. Comment below or send us an email.

How can you kill your loader?

Here is how you can fix your Magento 2 image loader speed.

  • If you’re a developer you can check out our git hub project.  You surely will need to make some adjustments in your theme though.
  • Email us [email protected] we are putting together a program where we can help merchants out by fixing their loader starting at about $500 US.
  • Beg your theme developer to fix the issue by sending them a link to this post
  • “But my developer said it was not possible to fix this?”  Well, they were wrong. Let us know if you need help.

Join the Movement, Kill the Loader!

As I look around at other sites, themes and trends the practice of adding image loaders are all too prevalent.  It’s bad UX! It seems someone came up with the concept as part of some widget and then everyone copied this bad construct. Over, and over and over again!!!! Additionally with AJAX here to stay many features of pages pop in after the initial load, so pages end up with a lot of motion. That may be OK for an app, but for consumer-facing websites, you want rock solid page load time with a solid stable viewport. I get very picky on motion, we need to eliminate as much as possible, don’t push things down, over across or up.  Load the page in the viewport and it should stay as solid as possible. Motion is awkward at best but surely will kill the perception of speed as things move and change on the page, it looks incomplete (because it is) to the user. #killtheloader

17 thoughts on “Kill the Loader – How to Improve Your Magento 2 Product Page Load Time

  1. I totally agree with all you said in article and I am facing same problem myself right now. Thank you for the code, I will check it right now.

    PS: Fix the link “fast image load Magento 2 extension on GitHub”

    Best regards!

  2. Your post doesn’t address the fact that this spinning issue gets progressively worse over time until the browser cache is emptied. From then on, it spins very briefly (true, could still do without the spinner altogether) and displays the image very quickly thereafter.

    Before emptying the browser, it’s banner/ajax/load/?sections which is held up according to Chrome’s networking tab. I understand it’s a cache busting, hole punching mechanism to load private user data as well as FPC cached data. For whatever reason, when the browser cache fills up, this process gets slower and slower.

    So it makes me wonder, even if we remove the spinner and hence fix the image loading part, the actual problem of a hanging AJAX call might still remain.

  3. Well maybe I should dig into why the cache flush works, I actually never tried it as I figured it was built wrong to begin with, loading the first image is the way to go, so I figured why not pursue that route. It’s built to have the spinning thing loaded at all times in the first-page load though, so no matter how fast it works you will probably see it a spin, and if you see it at all its too long for me.

    It’s ironic that you mention the ajax as we have been doing a lot of work optimizing lately and that is a huge performance bottleneck, it is a contributor to the this issue as you mention. There are several issues around the AJAX calls. One is that the PHP sessions use read locking and effectively if you have 5 AJAX calls they all will process almost sequentially (ugh). This means that if the JS on the page is dependent on the callback for the AJAX return you have to wait for it to be done for it to be run. The other is that there is 0 caching on any of them. Though that should be the case for the cart and some other ones, it should be a developer option as even putting 5 minutes on these can greatly improve the user experience. For example, a theme we use has an ajax call to load a promo message, perfect for a 1-hour cache or maybe even 5-minute cache. (And would help solve the session locking issue)

    The session issue is a difficult one and one that is problematic in M2 with its use of Ajax for private data. Here is a good write up not specific to M2 but a good one. https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/

  4. hey, thanks for the nice plugin!
    its helps us a lot. however, we noticed that if the images have different sizes, the first image can be seen in the background of later smaller images. can this be fixed?

  5. Thanks. If you have an example of what you are talking about it may help, I would say yes it can, this is mostly just a prototype to show how its done for the default Magento 2 theme. Adjustments can be made, there are still some challenges with how Fotorama alters it during the load but generally, you can get it to do what you need it to. We will look at that the next time we are in changing this code up.

  6. I just tried your extension on a dev site and it works great except for one thing:

    The placeholder image doesn’t actually go away, it’s always there behind the current image. This is a big deal since the we have various image sizes, the largest of which is usually that first image so that lingering image sticks out from behind around the subsequent images when you scroll through them.

    I’ve taken a look at fotorama.js and hav enot figured out what’s missing or needed to eliminate that placeholder image once the rest are loaded.

  7. We will have to look at that for the next update, I think spinner also has a CSS class, I think I had experimented with setting that to display none in the CSS to make it not show up.

  8. The div that is the spinner is commented out in the phtml.
    I’ll keep looking and post of I find a simple solution.

  9. hey, regarding the images overlapping: if you add this to the css section of the gallery.phtml of the plugin the background image will at least be hidden:

    .fotorama__wrap–slide .fotorama__stage__frame {
    background: white;

  10. The “background: white;” css is a lot easier but it’s causing the gallery to flash when the images are finally loaded so it’s not a great solution.

    What I came up with is a little bit involved and requires editing both the gallery.phtml and fotorama.js files.
    *** NOTE: This is supplied as is with no grantee it will work for you. ***

    * In gallery.phtml add a unique class to the element that is loading the image as a placeholder.
    line 62: Add the class fotorama__placeholder to the div element.

    * In fotorama.js I essentially recreated the actions done with the spinner but ended with deleting the element.
    1. Define the class added above.
    2. Create a jQuery object to that element.
    3. Remove the element after everything is loaded.

    Accomplished by add the following 3 lines:
    After line 92 (after : fotoramaSpinnerClass = _fotoramaClass + ‘__spinner’, ) add
    placeholderImageClass = _fotoramaClass + ‘__placeholder’,

    After line 1799 (after : $spinner = $fotorama.find(cls(fotoramaSpinnerClass)), ) add
    $placeholder = $fotorama.find(cls(placeholderImageClass)),

    After line 2427 (after : $spinner.removeClass(spinnerShowClass); ) add

  11. Great catch – was very helpful to solve this pain. Thanks

    But it just introduced a new pain for my OCD – Why i heavens name is it necessary to make a main menupoint under Stores -> Configuration, when there only is an option to enable the module, and why with a name no one knows whats behind. (XUMULUS)

    If you really feel its necessary that it can be enabled/disabled from admin ( I dont think it is, since this is a bugfix for developers, not end-customers) then please at least add it under Catalog in the configuration.

    But you get a plus points for not having it in the main menu or add a logo/picture beside the menu, which many extension developers think are the best option…

  12. Thanks for the feedback, it’s actually a pet peeve of mine as well, especially taking up the main menu space. I originally did not want the admin option at all but my developers just used one of our templates for our extensions to start, so we have not changed it yet. I was thinking it more often would be used as a sample that coders could move into their theme files or something like that. I will see if we can do to remove it on the next update. I mostly did this so we can gather some feedback and figure out what we can do for a core update at some future date, so it just works better out of the box.

  13. @Martin – I can’t speak for Dan but as an M2 developer, I find it very worthwhile to have an easy access on/off switch for testing purposes so I don’t need to run to SSH and wait for the paint to dry every time I need to compile to turn something on or off. 🙂

Comments are closed.