All posts
Forward Development

How to find what is actually slowing your Magento 2 storefront

An ordered way to diagnose a slow Magento 2 store: split server time from front-end weight, verify caching, then profile blocks and extensions.

  • Magento 2
  • Adobe Commerce
  • Operations

Someone reports that the store feels slow. Category pages take a few seconds to show anything, and product pages are worse on mobile. A ticket gets opened, and within a day it lists the usual fixes: enable JS bundling, minify CSS, add a CDN, try a lighter theme, upgrade the server. Some of those get done. The store feels about the same, and nobody can say why, because nobody measured anything before changing it.

Most Magento 2 speed advice is a checklist, and checklists treat every slow store as the same store. It isn't. A store that takes four seconds to send its first byte has a different problem from one that sends HTML in 200 ms and then loads three megabytes of JavaScript. The fixes don't overlap much. What follows is the order we run diagnostics in, so the first fix you make is the one that matters.

Split server time from front-end weight first

Before opening any config, find out where the time goes. Run this against a category page, a product page and the home page:

curl -s -o /dev/null -w 'ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/some-category.html

Run it a few times. The first request may be a cache miss and later ones should be hits. Then load the same URLs in Chrome DevTools or WebPageTest with a throttled mobile profile and note when content becomes visible.

Now you know which half you have:

  • TTFB is high (well over half a second) even on repeat requests. The problem is on the server: caching, PHP, the database, or extensions. Front-end work won't help yet.
  • TTFB is low but the page still renders slowly. The problem is in the browser: JavaScript, images, fonts, third-party tags.
  • Both are bad. Fix the server first. A slow first byte delays everything after it.

Write these numbers down. They are your baseline, and without them you can't show that anything you changed made a difference.

Confirm the basics are actually on

This takes five minutes and catches more slow stores than any profiler:

bin/magento deploy:mode:show
bin/magento cache:status
bin/magento indexer:status
bin/magento indexer:show-mode

The mode should be production. Developer mode on a live store is more common than it should be. It compiles code on the fly, skips static content deployment and makes every page slower. All cache types should show as enabled. Any indexer stuck on "Reindex required", or set to Update by Schedule while cron isn't running, means the storefront is reading stale or unindexed data, and some of those reads are expensive. Check crontab -l for the web user and look at the cron_schedule table for jobs that have stopped running or keep piling up.

Measure the Full Page Cache hit rate, not just the setting

Full Page Cache being "enabled" doesn't tell you much. What matters is how many anonymous page views it actually serves. In production, Varnish is the right FPC backend. The built-in file cache works, but it still boots PHP for every request.

With Varnish in front, watch hits against misses under real traffic:

varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss -f MAIN.pass

A healthy catalog storefront should serve most anonymous category and product views from cache. If misses and passes dominate, find out why before doing anything else. The usual causes:

  • A block declared cacheable="false" in layout XML. A single one makes the whole page uncacheable. This often comes from an extension that wanted to show something per customer and took the easy route. Search for it: grep -rn 'cacheable="false"' app/code vendor/*/*/view.
  • Marketing query parameters such as gclid, fbclid and utm_* that the VCL doesn't strip, so every ad click becomes a unique cache key.
  • Cookies or headers set on anonymous requests that make Varnish pass instead of serving from cache.
  • Aggressive cache invalidation. An import or ERP sync that saves products one at a time can flush cache tags all day long.

When a page that should be cached isn't, varnishlog -q 'ReqURL ~ "some-category"' shows what Varnish decided and why.

Check Redis and the database under load

Redis should hold the default cache and sessions. Use separate databases or instances for them, so a cache flush doesn't log out every customer and a session spike doesn't evict cache keys. Check redis-cli info memory and info stats. If evicted_keys keeps climbing, Redis is too small or its eviction policy is wrong. Session locking can serialise requests from the same visitor, and that shows up as slow AJAX calls from customer sections and the minicart.

On MySQL, turn on the slow query log with a low threshold for an hour of normal traffic, then run EXPLAIN on the worst queries. Slow queries on a cached storefront usually come from custom collections that are missing indexes, layered navigation over many attributes, or an extension querying in a loop.

Profile slow blocks and extensions

If cached pages are fast but misses, checkout and customer pages are slow, profile PHP. Magento's built-in profiler (bin/magento dev:profiler:enable html on a staging copy) breaks a request down by block and observer. An APM such as New Relic, or a profiler such as Blackfire, gives you the same view with real traffic and call graphs.

What tends to show up:

  • Plugins on hot methods (price calculation, product loading, quote totals) that do I/O on every call.
  • Observers on frequently fired events that call external APIs inline.
  • Blocks that load full collections to render a count or a single item.

To confirm a suspect, disable the module on staging with bin/magento module:disable Vendor_Module, recompile, and measure again against your baseline. Change one thing at a time. Ten modules turned off at once tell you nothing.

Treat front-end weight as its own job

Once TTFB is low and stays low, look at the browser side. Use Lighthouse and the DevTools Coverage tab to find unused JavaScript and CSS. Use the network panel to see what third-party tags cost: chat widgets, review platforms, tag managers loading other tag managers. Those tags often outweigh the theme.

Be careful with Magento's built-in JS bundling. On many stores it produces large bundles that load on every page whether they're needed or not, and over HTTP/2 that can make things worse. Measure with bundling on and off rather than assuming it helps. Image formats, lazy loading and font loading are usually easier wins than changing the theme.

What to do this week

Record TTFB and render timings for three representative pages. Run the four bin/magento status commands and fix anything wrong. Pull a Varnish hit rate and grep for cacheable="false". Those three steps tell you which half of the problem you have, and usually which fix comes first. Leave theme and bundling work until the server side is fixed.

If the numbers point to extension conflicts, cache invalidation from integrations, or something deeper in the stack, that's the kind of operational work we do on our Magento 2 practice. Get in touch with your baseline numbers, and we can tell you where we'd look next.

Need this done on a real stack?

Magento 2, Adobe Commerce, migrations to Medusa.js or Vendure, enterprise Next.js, WordPress, and AI automation.

Contact us