Magento 2 FPC Headache: X-Magento-Vary Cookie Corruption with Custom Controllers

Unmasking the X-Magento-Vary Cookie Bug: FPC Challenges with Custom Controllers

The Magento 2 Full Page Cache (FPC) is a cornerstone of performance, relying heavily on the X-Magento-Vary cookie to differentiate cached content based on user context (e.g., logged-in status, store, currency). However, a subtle yet critical bug has been identified that can lead to this cookie becoming corrupted, effectively bypassing FPC for specific types of custom controllers and severely impacting site performance.

This issue primarily affects Magento 2 stores utilizing custom frontend controllers that implement Magento\Framework\App\ActionInterface directly, rather than extending the more common Magento\Framework\App\Action\AbstractAction. When such a controller is invoked, especially in a session with a logged-in customer, the X-Magento-Vary cookie can begin to alternate between two different hash values on subsequent page loads. This 'flip-flop' behavior prevents pages from being served from cache, forcing a full page generation for every request.

The Technical Deep Dive: A Missed Migration

The root cause of this problem lies in an incomplete migration during a significant Magento 2 refactor (commit 344c83c, part of PR #16268). This refactor aimed to eliminate the need for inheritance for action controllers by moving various context plugins from targeting AbstractAction::dispatch() to ActionInterface::execute(). While several crucial plugins, such as those for Customer, Tax, and Weee context, were successfully migrated, the Magento\Store\App\Action\Plugin\Context plugin was overlooked.

Consequently, the Store context plugin remains registered on AbstractAction::dispatch(). When a controller implementing ActionInterface directly is executed, Magento's FrontController::getActionResponse() takes an alternative path that calls execute() directly, bypassing the dispatch() method of AbstractAction. This means the beforeDispatch plugin for the Store context never fires for these controllers.

The direct consequence is that vital store and currency information (store and current_currency) are never injected into the HTTP context for these requests. When getVaryString() generates the X-Magento-Vary hash, these missing parameters result in a different hash. The system then detects a mismatch and overwrites the browser's cookie, leading to the observed alternating values:

// Example of alternating X-Magento-Vary values:
// Hash A (correct): {"store":"de_de","current_currency":"EUR","customer_group":"1","customer_logged_in":true}
// Hash B (corrupted): {"customer_group":"1","customer_logged_in":true} (store/currency missing)

Proposed Fix and Workaround

The proposed fix involves migrating Magento\Store\App\Action\Plugin\Context to target ActionInterface via its beforeExecute method, consistent with the other context plugins. This would require injecting RequestInterface into the plugin's constructor to access necessary request parameters. The di.xml configuration would change from:


    

to:


    

For developers encountering this issue who cannot immediately apply a core patch, a workaround exists for controllers that do not require FPC. By calling $response->setMetadata('NotCacheable', true) at the beginning of the execute() method, you can prevent HttpPlugin from updating the Vary cookie, thus avoiding the corruption. However, this only mitigates the cookie issue for non-cacheable pages and does not address the underlying inconsistency in the Store context plugin.

Understanding such nuances of Magento's plugin system and FPC interactions is crucial for robust custom development and ensuring optimal performance on Adobe Commerce and Open Source platforms.

Start with the tools

Explore migration tools

See options, compare methods, and pick the path that fits your store.

Explore migration tools