← Back to blog

Slow Magento 2 Category Pages: A Profiling Checklist

Rodrigo Biazzi

Category pages are where Magento 2 performance problems usually show up first. They combine layered navigation, a product collection with prices for the current customer group, images, swatches, reviews and whatever third party blocks are on the listing. When a category takes several seconds to render, the cause is rarely one thing, and guessing wastes days.

This is the checklist I work through on Magento Open Source and Adobe Commerce 2.4.x. It starts with measurement, because every later step depends on knowing where the time goes.

1. Confirm the basics before profiling

Some problems are configuration, not code. Check these first, they take minutes:

  • Production mode. bin/magento deploy:mode:show should say production. Developer mode compiles code and static files on the fly and is far slower.
  • All caches enabled. bin/magento cache:status. A disabled layout, block_html or full_page cache changes everything.
  • Indexers on schedule and current. bin/magento indexer:status. Indexers marked as invalid or with a large backlog mean the storefront reads stale or incomplete index tables.
  • Cron running. With indexers on Update by Schedule, nothing is reindexed unless cron runs. Check cron_schedule for jobs stuck in running or piling up as pending.
  • Cache and session backends. Redis or Valkey for cache and sessions, not the file system or database.
  • Search engine health. Layered navigation and category listing go through OpenSearch. A cluster in yellow or red state, or one starved of heap, shows up as slow category pages.
bin/magento deploy:mode:show
bin/magento cache:status
bin/magento indexer:status
bin/magento indexer:show-mode
curl -s "http://localhost:9200/_cluster/health?pretty"

2. Separate cached and uncached performance

A category served from full page cache should come back in tens of milliseconds. If it does not, the problem is the cache, not PHP. Test both cases separately:

curl -s -o /dev/null -w "%{time_starttransfer}\n" -I https://example.com/women/tops.html
curl -sI https://example.com/women/tops.html | grep -i -E "x-magento-cache|age|cache-control"

If cached responses are fast but real users still see slow pages, you have a hit rate problem. Marketing query strings, a cacheable="false" block in a layout that applies to categories, or a cookie that varies the cache can all push most traffic past the cache. I cover that in detail in the article on Varnish and full page cache.

The rest of this checklist is about the uncached render, which every visitor hits after a cache flush, on every filtered URL that has not been cached yet, and on every sort and page variation.

3. Profile one uncached request

Do not optimise from intuition. Get a trace of a single uncached category request.

Built in profiler

Magento ships with a basic profiler. It is enough to see which blocks and layout steps take the time:

bin/magento dev:profiler:enable html
# load the category page, the timings appear at the bottom
bin/magento dev:profiler:disable

Run it on a staging copy, not on production, since the output is visible to anyone loading the page.

A real profiler

Blackfire, Tideways, New Relic or SPX show the full call graph with SQL queries and external calls. Look for three things:

  • Total SQL query count. A category page with a few hundred queries is common. Thousands means something loads data per product.
  • Functions with high exclusive time called once per product in the listing.
  • HTTP calls to external services during rendering. These should almost never happen on a category page.

4. Look for per product loads in the listing

A very common cause of slow categories in custom code is an N+1 pattern: something in the product list template or a plugin loads a full product, stock item or custom entity for each product in the collection.

<?php foreach ($block->getLoadedProductCollection() as $product): ?>
    <?php $full = $productRepository->getById($product->getId()); ?>
    <?= $escaper->escapeHtml($full->getData('badge_label')) ?>
<?php endforeach; ?>

With 36 products per page that is 36 full product loads, each with its own set of queries for EAV attributes, media gallery and more. The fix is to add the attribute to the collection once, before it loads. Marking the attribute as Used in Product Listing in the admin adds it to the listing collection. In code, a plugin on the collection works:

<?php
declare(strict_types=1);

namespace Acme\Catalog\Plugin;

use Magento\Catalog\Model\ResourceModel\Product\Collection;

class AddBadgeToListing
{
    public function beforeLoad(Collection $subject, $printQuery = false, $logQuery = false): array
    {
        if (!$subject->isLoaded()) {
            $subject->addAttributeToSelect('badge_label');
        }

        return [$printQuery, $logQuery];
    }
}

Scope a plugin like this carefully. Product collections are used everywhere, so attaching it globally adds the attribute to every collection on the store. A layout argument or a plugin on the specific list block is usually the better target.

Also check for stock checks, review summaries, wishlist status or custom price logic calculated per product. Each one should load data for the whole page in one query and keep it in memory.

5. Layered navigation and filters

Layered navigation builds its counts from OpenSearch aggregations. Slowness here usually comes from:

  • Too many filterable attributes. Every attribute set as filterable adds an aggregation. Review which attributes really need to appear in navigation on category pages.
  • Attributes with thousands of options. Each option is a bucket in the aggregation and an item in the rendered HTML.
  • Third party layered navigation modules that add their own queries on top of the core ones, or that disable the full page cache for filtered URLs.

Compare render time of the same category with and without layered navigation by temporarily setting the category Anchor setting to No on staging. If the gap is large, focus here.

6. Product count, sorting and pagination

  • Products per page. Allowing All on category pages turns a 36 product listing into a 3000 product one. Remove it from the allowed values in Catalog, Storefront.
  • Sort options. Only attributes marked as Used for Sorting in Product Listing should be offered for sorting. Every extra sort option is another set of URLs that misses the cache and needs its own uncached render.
  • Deep pagination. Page 200 of a large category is expensive for the search engine and rarely has value for users or SEO. Consider limiting what gets crawled.

7. Configurable products, prices and swatches

Configurable products are heavier on listings than simple ones. The price shown is calculated from children, and swatches on the listing need child data for every option.

  • Check that the price index is up to date. A stale or invalid catalog_product_price index leads to wrong or missing prices on the listing.
  • Swatches on the product listing (Stores, Configuration, Catalog, Storefront, Show Swatches in Product List) load swatch data for each configurable product. On large configurables this adds up. Turn it off on staging to measure the cost, then decide.
  • Many customer groups multiply the price index size. Tier prices and catalog price rules add more. That mostly affects reindex time, but a huge price index also slows the listing queries.

8. Images

Image work should never happen during a page request. If product images have not been resized yet, Magento generates them on first request, and a category page can trigger dozens of resizes at once.

bin/magento catalog:images:resize

Run it after imports, after changing image sizes in etc/view.xml of the theme and after deploying a new theme. On large catalogs it takes a while, so run it in the background or in batches. Also check that the media folder is on fast storage. Category pages read many small files, and a slow network file system shows up in every uncached render.

9. Blocks and layout

  • Search all layout XML that applies to catalog_category_view and default handles for cacheable="false". One of these disables full page cache for the whole page.
  • Look for blocks that load their own product collection, such as related products, recently viewed or promo widgets placed in the category sidebar. Each is another full collection load.
  • CMS blocks and widgets inside the category description run on every uncached render. A product list widget inside a category description is a hidden second listing.
  • Menu rendering with very large category trees can be slow. Check the depth configured for the top menu and whether a third party menu loads all categories with all attributes.
grep -rn 'cacheable="false"' app/code app/design vendor/*/*/view/frontend/layout

10. Database and server

When application code is clean and the page is still slow, look at the infrastructure:

  • Enable the MySQL slow query log for a short period with a low threshold and review what category requests generate.
  • Check InnoDB buffer pool size against the size of the catalog tables and indexes. If the working set does not fit in memory, every uncached category hits disk.
  • Check PHP-FPM worker counts and OPcache settings. OPcache must be enabled with enough memory to hold the whole compiled codebase, and opcache.validate_timestamps can be off in production when deploys reset it.
  • Check that cron jobs, imports and reindexes do not run on the same database at peak hours without limits.

Measure again after every change

Change one thing, flush only what is needed, profile the same URL again and write down the number. Some changes look good and do nothing. Others help on one category and hurt on another. A short table with the URL, the change and the before and after times is enough to keep the work honest and to show the client what moved.

FAQ

Why is my Magento 2 category page slow only the first time?

The first request renders the page without full page cache and may also trigger image resizing. Later requests are served from cache. Measure the uncached render separately, because filtered, sorted and paginated URLs often miss the cache too.

Should I enable the flat catalog to speed up category pages?

No. Adobe has deprecated the product flat catalog and does not recommend it for 2.4. Category listing goes through OpenSearch, so the flat tables bring indexing cost without a clear gain.

How many products per page should a Magento category show?

There is no fixed rule, but each product adds rendering work for prices, images and swatches. Remove the All option, keep the default page size moderate and measure the render time at the sizes you allow.

Which tool is best for profiling Magento 2?

The built in profiler shows block and layout timings and is enough for a first look. For SQL counts and full call graphs use a dedicated profiler such as Blackfire, Tideways, New Relic or SPX on a staging copy.

I am available for freelance Magento and Adobe Commerce work, from module development and performance audits to migrations and Hyvä builds. If that sounds useful, get in touch.

Related articles