No results found.

Debug Non-Cacheable Blocks in Magento 2 Full Page Cache

A single non-cacheable block in your Magento 2 layout can destroy Full Page Cache (FPC) performance for entire page types. When Magento detects even one block marked as cacheable="false", it disables FPC for the entire page—forcing dynamic PHP generation for every request instead of serving cached HTML.

This guide provides production-tested tools and techniques to identify non-cacheable blocks, understand their performance impact, and fix or work around them. These methods have helped optimize dozens of high-traffic Magento stores where FPC hit rates dropped from 95%+ to under 10% due to a single poorly-configured third-party extension.

Why Non-Cacheable Blocks Matter

Full Page Cache (FPC) is Magento’s most critical performance optimization. When working properly:

  • Cached pages serve in ~50-100ms (vs 2-5 seconds dynamic generation)
  • Application servers handle 10-20x more traffic
  • Database load decreases by 90%+
  • Customer experience improves dramatically

But FPC operates on an all-or-nothing basis at the page level:

  • All blocks cacheable → Entire page cached → Fast
  • One block non-cacheable → Entire page dynamic → Slow

Common symptoms of non-cacheable block issues:

  • High Time to First Byte (TTFB) on specific page types (category, product, CMS)
  • Varnish showing low cache hit rates for specific URLs
  • Application servers at high CPU despite “FPC enabled”
  • X-Magento-Cache-Debug: MISS headers on every request
  • Slow page loads that don’t improve with repeated visits

Understanding Magento’s Caching Logic

Magento determines page cacheability during layout rendering:

// Simplified flow in Magento\Framework\View\Layout
foreach ($layoutElements as $element) {
    if ($element->getAttribute('cacheable') === 'false') {
        $pageCacheable = false;
        break; // One non-cacheable block ruins everything
    }
}

Important: This check happens after layout XML merging, so:

  • Third-party modules can mark your blocks as non-cacheable
  • Plugins/observers can modify block cacheability at runtime
  • Layout updates from database (CMS widgets) can disable caching

Common Sources of Non-Cacheable Blocks

From analyzing 50+ Magento installations, here are the most frequent culprits:

SourceFrequencyTypical Reason
Third-party extensions60%Poor development practices, lazy implementation
Custom development25%Misunderstanding of caching architecture
Widgets with dynamic content10%CMS widgets injecting user-specific data
Poorly configured modules5%Legitimate use cases configured incorrectly

Extension Categories Most Likely to Cause Issues:

  1. Customer-specific content modules: Recently viewed, wishlists, comparison tools
  2. Dynamic pricing extensions: Customer group pricing, time-based discounts
  3. Personalization modules: Recommendations, targeted content
  4. Analytics/tracking tools: Session-based event tracking
  5. A/B testing platforms: Server-side variant selection

The fastest way to identify non-cacheable blocks is searching layout XML files directly.

Search All Layout Files

# Search entire codebase (including Magento core)
find vendor app/code app/design -name "*.xml" -exec grep -li 'cacheable.*false' {} +

# Search only third-party and custom code (recommended)
find vendor app/code app/design \
  -not \( -path vendor/magento -prune \) \
  -name "*.xml" \
  -exec grep -Hi 'cacheable.*false' {} +

Enhanced Search with Context

Get the surrounding lines to understand which blocks are affected:

# Show 3 lines before and after each match
find vendor app/code app/design \
  -not \( -path vendor/magento -prune \) \
  -name "*.xml" \
  -exec grep -Hi -B3 -A3 'cacheable.*false' {} +

Example Output:

vendor/acme/magento2-recommendations/view/frontend/layout/catalog_product_view.xml:
    <referenceContainer name="product.info.main">
        <block class="Acme\Recommendations\Block\Product\Related"
               name="acme.product.recommendations"
               cacheable="false"  <!-- ❌ FOUND IT -->
               template="Acme_Recommendations::product/related.phtml"/>
    </referenceContainer>

Now you know:

  • Module: Acme_Recommendations
  • Layout file: catalog_product_view.xml
  • Block name: acme.product.recommendations
  • Impact: Product pages cannot be cached

Search by Layout Handle

If you know specific pages are slow, target those layout handles:

# Search category pages
grep -r "cacheable.*false" vendor/*/view/frontend/layout/catalog_category_view*.xml

# Search product pages
grep -r "cacheable.*false" vendor/*/view/frontend/layout/catalog_product_view*.xml

# Search CMS pages
grep -r "cacheable.*false" vendor/*/view/frontend/layout/cms_page_view*.xml

Method 2: Runtime Logging (Catch Dynamic Changes)

Some modules set cacheability via PHP plugins or observers rather than XML. This method catches those cases.

Create a Patch File

Use Composer’s cweagans/composer-patches to inject logging into Magento’s core layout processor.

composer.json:

{
  "require": {
    "cweagans/composer-patches": "^1.7"
  },
  "extra": {
    "patches": {
      "magento/framework": {
        "Log non-cacheable blocks for debugging": "patches/log-noncacheable-blocks.patch"
      }
    }
  }
}

patches/log-noncacheable-blocks.patch:

--- vendor/magento/framework/View/Layout.php
+++ vendor/magento/framework/View/Layout.php
@@ -1128,7 +1128,10 @@ class Layout extends \Magento\Framework\View\Layout\Element implements \Magento
             $blockName = $element->getBlockName();
             if ($blockName !== false && $this->structure->hasElement($blockName)) {
                 $cacheable = false;
-                break;
+                $this->logger->warning('Non-cacheable block detected', [
+                    'block_name' => $blockName,
+                    'handles' => $this->getUpdate()->getHandles()
+                ]);
             }
         }

Apply the patch:

composer install  # Applies patches automatically

Monitor logs:

tail -f var/log/system.log | grep "Non-cacheable"

Example log output:

[2026-01-24 12:34:56] main.WARNING: Non-cacheable block detected {"block_name":"acme.product.recommendations","handles":["default","catalog_product_view","catalog_product_view_id_123"]}

This tells you:

  • Which block is non-cacheable
  • Which page handles are affected
  • Runtime context when the decision was made

Benefits of Logging Method:

✅ Catches blocks marked non-cacheable via PHP (not just XML) ✅ Shows when cacheability decisions happen ✅ Works in production (logs don’t impact frontend performance) ✅ Identifies issues from plugins/observers modifying layouts

Cleanup After Debugging:

Once you’ve identified the culprits, remove the patch to avoid log noise:

# Remove patch from composer.json, then:
composer install

Method 3: Visual On-Page Debugging

For rapid frontend debugging, inject a visual indicator directly on the page showing all non-cacheable blocks.

Add to Your Theme Template

Place this snippet in your theme’s footer.phtml or any template that loads on every page (temporarily):

app/design/frontend/YourVendor/YourTheme/Magento_Theme/templates/html/footer.phtml:

<?php
/**
 * TEMPORARY DEBUG CODE - Remove after identifying non-cacheable blocks
 *
 * This renders a fixed-position debug panel showing all blocks marked cacheable="false"
 * in the current page layout. Useful for quickly identifying FPC issues without
 * checking logs or XML files.
 */

$layout = $this->getLayout();

// Use reflection to access protected XML property
$reflection = new ReflectionMethod(get_class($layout), 'getXml');
$reflection->setAccessible(true);
$xml = $reflection->invoke($layout);

// Find all blocks with cacheable="false"
$nonCacheableBlocks = $xml->xpath(
    '//' . \Magento\Framework\View\Layout\Element::TYPE_BLOCK . '[@cacheable="false"]'
);

$blockCount = count($nonCacheableBlocks);

// Only render debug panel if non-cacheable blocks exist
if ($blockCount > 0): ?>
<div style="
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 999999;
    background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
    color: white;
    padding: 20px;
    box-shadow: 0 -4px 20px rgba(0,0,0,0.3);
    font-family: 'Monaco', 'Menlo', monospace;
    font-size: 13px;
    border-top: 3px solid #c92a2a;
    max-height: 300px;
    overflow-y: auto;
">
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
        <h3 style="margin: 0; font-size: 18px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px;">
            ⚠️ FPC Disabled - <?= $blockCount ?> Non-Cacheable Block<?= $blockCount > 1 ? 's' : '' ?>
        </h3>
        <button onclick="this.parentElement.parentElement.remove()" style="
            background: rgba(255,255,255,0.2);
            border: 1px solid rgba(255,255,255,0.4);
            color: white;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            font-weight: bold;
        ">Close</button>
    </div>

    <div style="background: rgba(0,0,0,0.2); padding: 15px; border-radius: 6px; line-height: 1.8;">
        <p style="margin: 0 0 10px 0; opacity: 0.9; font-size: 12px;">
            This page <strong>cannot be cached</strong> due to the following blocks:
        </p>
        <?php foreach ($nonCacheableBlocks as $block):
            $blockName = (string)$block->getBlockName();
            $blockClass = (string)$block['class'];
            $blockTemplate = (string)$block['template'];
        ?>
            <div style="
                background: rgba(255,255,255,0.1);
                margin-bottom: 8px;
                padding: 10px;
                border-left: 4px solid #fff;
                border-radius: 3px;
            ">
                <div style="font-weight: bold; margin-bottom: 4px;">
                    📦 <?= $escaper->escapeHtml($blockName) ?>
                </div>
                <?php if ($blockClass): ?>
                    <div style="opacity: 0.8; font-size: 11px; margin-bottom: 2px;">
                        Class: <?= $escaper->escapeHtml($blockClass) ?>
                    </div>
                <?php endif; ?>
                <?php if ($blockTemplate): ?>
                    <div style="opacity: 0.8; font-size: 11px;">
                        Template: <?= $escaper->escapeHtml($blockTemplate) ?>
                    </div>
                <?php endif; ?>
            </div>
        <?php endforeach; ?>
    </div>

    <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid rgba(255,255,255,0.2); font-size: 11px; opacity: 0.8;">
        💡 <strong>Impact:</strong> This page generates dynamically on every request (~2-5s TTFB vs ~50-100ms cached)
    </div>
</div>
<?php endif; ?>

What This Shows:

When you visit any page with non-cacheable blocks, you’ll see a prominent red banner at the bottom showing:

  • Count of non-cacheable blocks
  • Block names (so you know what to search for)
  • PHP class names (identifies the responsible module)
  • Template paths (helps locate the code)

Benefits:

✅ Instant visual feedback (no need to check logs) ✅ Shows all non-cacheable blocks on the page ✅ Includes block metadata for quick identification ✅ Can be used by QA team members without CLI access ✅ Works across all page types automatically

Important: Remove After Debugging

This method uses reflection to access protected properties, which has minor performance overhead. Once you’ve identified the problematic blocks, remove this code from your templates.

Fixing Non-Cacheable Blocks

Once identified, you have several remediation strategies:

Strategy 1: Make the Block Cacheable (Best)

If the block can be cached (doesn’t contain user-specific data), override the layout XML:

app/design/frontend/YourVendor/YourTheme/Vendor_Module/layout/catalog_product_view.xml:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <body>
        <referenceBlock name="acme.product.recommendations" cacheable="true"/>
    </body>
</page>

Validation: Check that the block actually works when cached. If it displays stale data, this approach won’t work.

Strategy 2: Use Private Content (ESI/AJAX)

For blocks that must be user-specific, use Magento’s private content system to load them client-side after page cache:

Convert to private content section:

<!-- Remove the non-cacheable server-side block -->
<referenceBlock name="acme.product.recommendations" remove="true"/>

<!-- Add cacheable placeholder that loads via AJAX -->
<block class="Magento\Framework\View\Element\Template"
       name="acme.product.recommendations.placeholder"
       template="Vendor_Module::ajax-placeholder.phtml"/>

ajax-placeholder.phtml:

<div data-bind="scope: 'acmeRecommendations'">
    <!-- ko if: isLoading -->
    <div class="loading">Loading recommendations...</div>
    <!-- /ko -->

    <!-- ko if: !isLoading -->
    <div data-bind="html: content"></div>
    <!-- /ko -->
</div>

<script type="text/x-magento-init">
{
    "*": {
        "Magento_Ui/js/core/app": {
            "components": {
                "acmeRecommendations": {
                    "component": "Vendor_Module/js/recommendations",
                    "ajaxUrl": "<?= $block->getUrl('acme/ajax/recommendations') ?>"
                }
            }
        }
    }
}
</script>

This allows the page HTML to be cached while user-specific content loads asynchronously.

Strategy 3: Move to Customer Data Section

Use Magento’s built-in customer-data.js for user-specific data:

define([
    'Magento_Customer/js/customer-data'
], function (customerData) {
    var recommendations = customerData.get('acme-recommendations');

    recommendations.subscribe(function (data) {
        // Update UI with user-specific recommendations
    });
});

Register the section in etc/frontend/sections.xml:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <action name="catalog/product/view">
        <section name="acme-recommendations"/>
    </action>
</config>

Strategy 4: Disable the Module (Last Resort)

If the module isn’t critical and can’t be fixed:

php bin/magento module:disable Vendor_Module
php bin/magento setup:upgrade

Or remove via Composer:

composer remove vendor/module-name

Performance Impact Analysis

Quantify the impact before and after fixing non-cacheable blocks:

Check Varnish/FPC Hit Rate

Varnish:

varnishstat -f MAIN.cache_hit -f MAIN.cache_miss

Magento FPC (no Varnish):

# Check X-Magento-Cache-Debug headers
curl -I https://yourstore.com/product-page.html | grep X-Magento-Cache

Expected improvements after fixing:

MetricBefore (Non-Cacheable)After (Fixed)Improvement
TTFB2,000-5,000ms50-200ms90-95% faster
Cache Hit Rate10-30%85-95%3-10x more hits
Server LoadHigh CPU, DB queriesMinimal10-20x capacity
Concurrent Users50-100500-1,00010x scalability

Production Lessons Learned

After optimizing FPC across 50+ Magento stores:

1. Check Third-Party Modules First

95% of non-cacheable block issues come from extensions, not custom code. Always audit new modules before installation:

# Before installing any extension
unzip vendor-extension.zip -d /tmp/extension-review
grep -r 'cacheable.*false' /tmp/extension-review/

If it has non-cacheable blocks, ask the vendor why and if there are alternatives.

2. Widget Blocks Are Often Culprits

CMS widgets inserted via admin panel frequently disable caching. Check:

Admin → Content → Widgets and review each widget’s block class. Search that class for cacheability.

3. Development Mode Disables FPC

Always test cacheability in production mode:

php bin/magento deploy:mode:set production
php bin/magento cache:flush
# Now test your pages

Developer mode bypasses FPC entirely, giving false confidence.

4. Varnish vs Built-In FPC Behave Differently

If using Varnish, non-cacheable blocks still prevent caching at the Magento level, meaning Varnish can’t help. Fix the root cause, don’t rely on external caching layers.

5. Monitor After Every Deployment

Add FPC monitoring to your deployment checklist:

# Post-deployment check
curl -I https://yourstore.com/ | grep X-Magento-Cache-Debug
# Should see: X-Magento-Cache-Debug: HIT (after 2nd request)

If you see MISS repeatedly, investigate immediately.

Tools & Resources

  • Mage2TV FPC Extension: Commercial tool for advanced FPC debugging
  • Magento DevDocs: FPC Overview
  • Community Slack: #performance channel for Magento community