Magevanta

Magento 2 客户数据区块与 localStorage 性能优化

每个 Magento 2 前台商店都使用 客户数据区块 — 这是迷你购物车、客户姓名显示、收藏夹计数器和结账摘要背后的机制。对购物者来说一切都很流畅,但在其内部可能悄无声息地破坏页面加载性能。

如果你曾经想知道为什么你的页面在初始加载后立即触发额外的 AJAX 请求,或者为什么你的 localStorage 会膨胀到几兆字节,这篇文章就是为你准备的。

什么是客户数据区块?

Magento 2 将页面渲染分为两个阶段:服务端(Astro/Varnish/FPC)和客户端(JavaScript)。由于全页缓存对所有访客提供相同的 HTML,个性化数据 — 购物车内容、已登录客户姓名、收藏夹数量 — 无法为缓存页面在服务端渲染。

引入 sections.xmlCustomer Data JS API

<!-- Vendor_Module/etc/frontend/sections.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd">
    <action name="checkout/cart/add">
        <section name="cart"/>
        <section name="checkout-data"/>
    </action>
</config>

Enter fullscreen mode Exit fullscreen mode

此文件告诉 Magento:「当执行 checkout/cart/add 操作时,失效 cartcheckout-data 区块。」 在下次页面加载时,JavaScript 检测到这些已失效的区块,并通过 customer/section/load/ AJAX 获取最新数据。

数据流程

  1. 页面加载来自 FPC/Varnish(无个性化)
  2. JS 初始化 Magento_Customer/js/customer-data
  3. localStorage 根据 sectionLoadUrl + storeId 键检查缓存的区块数据
  4. 已失效区块触发 POST /customer/section/load/ 并附带区块名称
  5. 响应更新 localStorage、ko.observables 和 UI(迷你购物车、消息等)

这听起来很高效,但存在三个主要性能陷阱

性能陷阱 #1:「区块地狱」AJAX 请求

开箱即用,Magento 2 的 customer-data 模块在首次未缓存命中时调用 customer/section/load/ 并附带每个已注册区块

// vendor/magento/module-customer/view/frontend/web/js/customer-data.js
// (simplified)
getFromServer: function (sectionNames) {
    return storage.post('customer/section/load/', {
        sections: sectionNames,
        update_section_id: true
    });
}

Enter fullscreen mode Exit fullscreen mode

典型的 Magento 安装注册了 15–25 个区块cartcheckout-datacomparisondirectory-datacustomerwishlistmessageslast-ordered-itemsreviewproduct_data_storagerecently_viewed_productrecently_compared_productpaypalbilling_agreementpersistent,以及扩展添加的任何自定义区块。

每个区块都需要在后端执行数据库查询

区块 典型后端成本
cart Quote 加载 + 商品 + 总计计算
customer Customer 实体加载 + 地址集合
wishlist Wishlist 商品集合
directory-data 国家/地区数据 + 运费缓存
checkout-data Quote 地址验证 + 配送方式解析

在高流量商店中,单次 section/load/ 调用可能需要 300ms–1.2s,具体取决于购物车复杂度、目录大小以及是否为会话存储配置了 Redis。

真正的杀手:它在每个页面运行

与购物车/结账页面(你期望一些后端工作)不同,此请求在每个缓存的 CMS 页面、分类页面和产品页面上触发。浏览 20 个产品的购物者会生成 20 次 section/load 请求 — 全部执行相同的工作。

性能陷阱 #2:localStorage 膨胀

区块数据存储在浏览器的 localStorage 中。Magento 的默认键格式为:

mage-cache-storage     // ~50KB–500KB+
mage-cache-storage-section-invalidation  // small

Enter fullscreen mode Exit fullscreen mode

cart 区块就可能增长到 100KB+,如果购物者有包含可配置商品、自定义选项和阶梯价的复杂购物车。乘以 20 个区块,你每位访客可能达到 1–2MB 的 localStorage

这会导致三个问题:

  1. 移动浏览器会积极清除 localStorage。在 iOS Safari 中,存储可能在内存压力下被清除,强制完全重新获取。
  2. 反序列化缓慢 — 对 1MB+ 字符串执行 JSON.parse() 会阻塞主线程 10–50ms,导致交互到下一绘制(INP)不佳。
  3. 会话存储配额 — 某些浏览器将 localStorage 限制为 5MB。一个大型购物车可能接近限制,破坏其他功能。

性能陷阱 #3:区块失效滥用

最常见的错误是:在每个控制器操作上声明区块失效

<!-- BAD: Extension invalidates everything on every page -->
<action name="*">
    <section name="cart"/>
    <section name="customer"/>
    <section name="wishlist"/>
</action>

Enter fullscreen mode Exit fullscreen mode

某些第三方扩展使用通配符(*)匹配,或在非购物车操作(如通过跟踪像素的产品列表页面浏览)上失效 cart。这会强制 section/load/ 调用在每个页面上触发,即使没有变化。

另一个滥用模式:对 getSectionData() 的插件为当前页面甚至不显示的数据运行昂贵的查询

优化策略

1. 审计你的 sections.xml 文件

首先,绘制出你实际正在失效的内容:

# Find all section definitions
grep -r "<action name=" app/code vendor/magento --include="sections.xml" | wc -l

# Find wildcard invalidation (highly suspicious)
grep -r '<action name="\*"' app/code vendor --include="sections.xml"

Enter fullscreen mode Exit fullscreen mode

对于每个通配符或过于宽泛的失效,询问:「此操作是否真的改变了该区块的数据?」

2. 从初始加载中消除未使用的区块

你可以通过 di.xml 配置哪些区块在初始请求时加载:

<!-- app/etc/di.xml or your module -->
<type name="Magento\Customer\CustomerData\SectionPoolInterface">
    <arguments>
        <argument name="sectionSourceMap" xsi:type="array">
            <!-- Only load what you actually display on every page -->
            <item name="cart" xsi:type="string">Magento\Checkout\CustomerData\Cart</item>
            <item name="customer" xsi:type="string">Magento\Customer\CustomerData\Customer</item>
            <item name="messages" xsi:type="string">Magento\Theme\CustomerData\Messages</item>
        </argument>
    </arguments>
</type>

Enter fullscreen mode Exit fullscreen mode

recently_viewed_productrecently_compared_productreview 这样的区块很少需要在初始 section/load/ 调用中加载。当相关小部件初始化时再延迟加载它们。

3. 禁用你不使用的区块

如果你不使用收藏夹或商品对比,完全移除它们:

<!-- app/code/Vendor/Module/etc/frontend/di.xml -->
<type name="Magento\Customer\CustomerData\SectionPool">
    <plugin name="disable_unused_sections"
            type="Vendor\Module\Plugin\DisableUnusedSections"
            sortOrder="10"/>
</type>

Enter fullscreen mode Exit fullscreen mode

<?php
namespace Vendor\Module\Plugin;

class DisableUnusedSections
{
    private array $disabledSections = [
        'wishlist',
        'comparison',
        'review',
        'last-ordered-items'
    ];

    public function afterGetSectionNames(\Magento\Customer\CustomerData\SectionPool $subject, array $result): array
    {
        return array_diff($result, $this->disabledSections);
    }
}

Enter fullscreen mode Exit fullscreen mode

这会从 section/load/ 调用和 localStorage 中移除这些区块。

4. 对临时数据切换到 sessionStorage

对于不需要跨标签持久化的区块(如 messages),覆盖存储后端:

<!-- app/code/Vendor/Module/etc/frontend/di.xml -->
<type name="Magento\Customer\CustomerData\JsLayoutDataProviderPool">
    <arguments>
        <argument name="components" xsi:type="array">
            <!-- Custom storage handler -->
        </argument>
    </arguments>
</type>

Enter fullscreen mode Exit fullscreen mode

或者使用更简单的方法:修补 customer-data.js 以对标签特定的区块使用 sessionStorage,减少 localStorage 压力。

5. 实现服务端推送(进阶)

不要让浏览器在每次页面加载时轮询,而是在数据实际变化时推送更新:

// In your observer, after cart modification
$sectionIdentifier = $this->sectionIdentifierFactory->create();
$sectionIdentifier->resetIdentifier(); // Forces JS to re-fetch on next action

Enter fullscreen mode Exit fullscreen mode

更好的方法是,对于无头/API 优先架构,完全绕过 customer-data 并使用 GraphQL 查询获取购物车状态,仅在迷你购物车打开时按需加载所需内容。

6. 为区块元数据使用 Redis

虽然区块数据本身在浏览器中,但后端仍在查询数据库。确保你的 customer/section/load/ 端点使用:

  • Redis 会话存储(而非数据库会话)
  • Quote 缓存通过 Magento_Quote 缓存标签
  • Varnish 直通用于 section/load/(不要缓存此端点 — 它必须是动态的)

7. 测量前后对比

使用 Blackfire 或 New Relic 追踪 customer/section/load/ 调用:

  1. 打开任意缓存的产品页面
  2. 检查网络标签 — 找到 section/load/ POST
  3. 分析它 — 查找 QuoteRepository::getTotalsCollector::collectAddressRepository::getList
  4. 应用优化并重新分析

优化后的预期结果:

指标 之前 之后
区块数量 22 6
AJAX 负载大小 45KB 8KB
section/load/ 后端时间 450ms 80ms
localStorage 大小 1.2MB 180KB
INP 改善 -40ms

总结

客户数据区块是一个强大但危险的功能。Magento 2 的默认配置过于慷慨 — 它在每个页面加载每个区块,在 localStorage 中存储兆字节数据,并假设所有扩展都知道如何负责任地失效。

解决方案是精准的:审计你的 sections.xml,消除未使用的区块,禁用你不需要的功能,并确保你的后端会话/缓存层针对 customer/section/load/ 端点进行了优化。

最快的 AJAX 请求是你不需要发起的那个。

觉得这有帮助?查看我们的 Magento 2 性能优化指南 2026 以获取完整的店面调优策略。