The Full Page Cache

The full page cache stores whole HTML responses; one uncacheable block disables it for the entire page.

Syntax<block class="..." name="..." cacheable="false"/>

The full page cache stores the complete HTML of a response and serves it again without rebuilding the page. Magento ships two implementations: the built-in one, which is the full_page cache type, and Varnish. Only GET and HEAD responses are ever cached.

What is not cached

A page is cacheable unless something on it says otherwise. The switch is the cacheable="false" attribute on a block in layout XML — the merged XML that declares which blocks make up a page, covered in Stage 4. One such block anywhere on the page makes the whole page uncacheable, because the flag is read per page, not per block. The customer edit form and checkout use it deliberately. Catalog and CMS pages must never carry it.

Variations

One URL can need several cached copies: customer group, store, currency and logged-in state all change the output. Magento puts that context into the X-Magento-Vary cookie, which becomes part of the cache key.

Private content

Per-customer data is not cached on the server at all. The cart summary and wishlist counter are fetched into browser local storage by the customer-data JavaScript component. Which action refreshes which section is declared in etc/frontend/sections.xml, and only POST and PUT requests trigger a refresh.

After editing layout XML run bin/magento cache:clean layout full_page; after editing sections.xml run bin/magento cache:clean config.

Example

Example · php
<?php
// app/code/Vendor/Loyalty/Controller/Points/Balance.php
declare(strict_types=1);

namespace Vendor\Loyalty\Controller\Points;

use Magento\Customer\Model\Session as CustomerSession;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultInterface;

class Balance implements HttpGetActionInterface
{
    public function __construct(
        private readonly JsonFactory $resultJsonFactory,
        private readonly CustomerSession $customerSession
    ) {
    }

    public function execute(): ResultInterface
    {
        $result = $this->resultJsonFactory->create();

        // A GET response is cacheable by default. These headers are
        // what keep a per-customer answer out of the page cache.
        $result->setHeader(
            'Cache-Control',
            'no-store, no-cache, must-revalidate, max-age=0',
            true
        );
        $result->setHeader('Pragma', 'no-cache', true);

        $result->setData([
            'points' => $this->customerSession->isLoggedIn() ? 120 : 0,
        ]);

        return $result;
    }
}

// GET /loyalty/points/balance returns 200 with a JSON body and no
// stored copy: every visitor gets their own answer. Register the
// route, then run: bin/magento cache:clean config

When to use it

  • Finding out why a product page is rebuilt on every request instead of being served from cache.
  • Adding a per-customer counter to the header without making the header uncacheable.
  • Returning a JSON endpoint that must never be stored by Varnish or the built-in page cache.

More examples

Turning the cache off for one page

This file applies only to the loyalty_points_history page, so only that page becomes uncacheable. The attribute is not valid on referenceBlock: the blockReferenceType in elements.xsd allows only name, class, template, group, display and remove, so cacheable there fails schema validation. To disable caching on a block someone else declared, redeclare it as a block with the same name and cacheable="false" in your own layout file, and layout merging applies the attribute to the existing block. Run bin/magento cache:clean layout full_page afterwards, or the previously cached version of the page keeps being served.

Example · xml
<!-- app/code/Vendor/Loyalty/view/frontend/layout/loyalty_points_history.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <block class="Magento\Framework\View\Element\Template"
                   name="loyalty.points.history"
                   template="Vendor_Loyalty::points/history.phtml"
                   cacheable="false"/>
        </referenceContainer>
    </body>
</page>

Declaring a private section

This says: when the storefront posts to loyalty/points/redeem, the loyalty-points section in browser storage is out of date and must be refetched. Magento compiles this map into the Magento_Customer/js/section-config component, and customer-data.js invalidates the listed sections in browser storage when it sees a state-changing request to that action, then refetches them from customer/section/load. The private_content_version cookie still tracks the version of private content, but the customer data invalidation mechanism no longer relies on it. Only POST and PUT actions work here, because a GET would be served from the page cache and the refresh would never fire. Run bin/magento cache:clean config after adding it.

Example · xml
<!-- app/code/Vendor/Loyalty/etc/frontend/sections.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd">
    <action name="loyalty/points/redeem">
        <section name="loyalty-points"/>
    </action>
</config>

The section source behind it

The map ties the section name from sections.xml to a class implementing Magento\Customer\CustomerData\SectionSourceInterface, whose getSectionData() returns the array the browser stores. This is the whole private-content mechanism: public HTML from the page cache, per-customer values fetched separately. The file lives in etc/frontend/ because the section pool only exists in the frontend area.

Example · xml
<!-- app/code/Vendor/Loyalty/etc/frontend/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Customer\CustomerData\SectionPoolInterface">
        <arguments>
            <argument name="sectionSourceMap" xsi:type="array">
                <item name="loyalty-points" xsi:type="string">
                    Vendor\Loyalty\CustomerData\Points
                </item>
            </argument>
        </arguments>
    </type>
</config>

Discussion

  • Be the first to comment on this lesson.