← Voltar ao blog

Customer data sections no Magento 2: como funciona o conteúdo privado

Rodrigo Biazzi

O Magento 2 faz cache de páginas inteiras para todos os visitantes, e mesmo assim o header mostra o nome certo, o minicart mostra os itens certos e as mensagens aparecem depois de adicionar ao carrinho. Isso funciona porque os dados pessoais não vêm do HTML em cache. Eles vêm das customer data sections, carregadas por JavaScript depois que a página é renderizada.

Entender esse mecanismo é a diferença entre uma loja em que o full page cache funciona e uma em que alguém colocou cacheable="false" para fazer uma saudação funcionar e desativou o cache de todas as páginas sem perceber. Este artigo explica como as sections funcionam no Magento 2.4.8 e como adicionar as suas.

O problema que as sections resolvem

Com full page cache, o primeiro visitante de uma URL gera o HTML e todos os seguintes recebem o mesmo HTML do Varnish ou do cache embutido. Qualquer coisa específica de um cliente, como nome, carrinho, contagem da wishlist ou uma mensagem de preço personalizada, não pode estar nesse HTML. Se estiver, o próximo visitante também vê.

A resposta do Magento é manter o HTML genérico e buscar o conteúdo privado separadamente:

  1. A página em cache é renderizada com placeholders ligados a componentes JavaScript.
  2. O módulo Magento_Customer/js/customer-data verifica o local storage do navegador para cada section.
  3. Se uma section está ausente, expirada ou invalidada, ele a pede para customer/section/load, que nunca entra em cache.
  4. Os componentes ligados àquela section se atualizam sozinhos, porque as sections são observables do Knockout.

Os dados ficam no local storage em mage-cache-storage, então a maioria das visualizações de página não faz nenhuma requisição extra. É também por isso que limpar cookies às vezes não limpa o que você vê no minicart, enquanto limpar o local storage limpa.

Sections nativas

Os módulos do core registram sections como cart, customer, messages, wishlist, compare-products, directory-data e last-ordered-items. Dá para ver a lista completa da sua loja abrindo a aba Application nas ferramentas de desenvolvedor do navegador e inspecionando mage-cache-storage.

Quando as sections são recarregadas

As sections são invalidadas de algumas formas:

  • sections.xml: cada módulo mapeia caminhos de controller actions para as sections que elas alteram. Quando o navegador envia um POST para uma dessas actions, por AJAX ou por um submit normal de formulário, o JavaScript marca essas sections como inválidas e as recarrega.
  • Lifetime: cada section expira depois do Customer Data Lifetime definido em Stores, Configuration, Customers, Customer Configuration, Online Customers Options. O config path é customer/online_customers/section_data_lifetime, em minutos.
  • O cookie private_content_version: o Magento grava um valor novo nesse cookie quando uma requisição POST chega ao servidor, o que cobre login, logout e a maioria das actions que alteram dados privados. Um valor diferente avisa o navegador que as sections em cache estão desatualizadas.
  • Manual: seu JavaScript pode chamar customerData.reload() ou customerData.invalidate().

Adicionando uma section customizada

Como exemplo, uma section com uma mensagem de progresso para frete grátis que depende do carrinho atual. Ela precisa de três partes: uma classe de section source, um registro no DI e um arquivo sections.xml.

1. A section source

<?php

declare(strict_types=1);

namespace Vendor\FreeShipping\CustomerData;

use Magento\Checkout\Model\Session as CheckoutSession;
use Magento\Customer\CustomerData\SectionSourceInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;

class FreeShippingProgress implements SectionSourceInterface
{
    public function __construct(
        private readonly CheckoutSession $checkoutSession,
        private readonly ScopeConfigInterface $scopeConfig
    ) {
    }

    public function getSectionData(): array
    {
        $threshold = (float) $this->scopeConfig->getValue(
            'carriers/freeshipping/free_shipping_subtotal',
            ScopeInterface::SCOPE_STORE
        );
        $subtotal = (float) $this->checkoutSession->getQuote()->getBaseSubtotalWithDiscount();

        return [
            'threshold' => $threshold,
            'remaining' => max(0.0, $threshold - $subtotal),
        ];
    }
}

Mantenha essa classe rápida. Ela roda em toda requisição de carregamento de section de todo visitante que dispara uma, e o customer/section/load não tem cache. Queries pesadas ou chamadas a APIs externas aqui vão aparecer como respostas lentas ao adicionar ao carrinho.

2. Registre em etc/frontend/di.xml

<?xml version="1.0"?>
<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="free-shipping-progress" xsi:type="string">Vendor\FreeShipping\CustomerData\FreeShippingProgress</item>
            </argument>
        </arguments>
    </type>
</config>

3. Invalide em 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="free-shipping-progress"/>
    </action>
    <action name="checkout/cart/delete">
        <section name="free-shipping-progress"/>
    </action>
    <action name="checkout/cart/updatePost">
        <section name="free-shipping-progress"/>
    </action>
    <action name="checkout/sidebar/removeItem">
        <section name="free-shipping-progress"/>
    </action>
    <action name="checkout/sidebar/updateItemQty">
        <section name="free-shipping-progress"/>
    </action>
</config>

Os nomes das actions são caminhos de route, controller e action. Esquecer uma é o motivo habitual de uma section mostrar dados velhos depois de determinada ação do usuário. Olhe o Magento_Checkout/etc/frontend/sections.xml para ver quais actions o core usa para a section cart e copie o padrão.

4. Use no JavaScript

define([
    'uiComponent',
    'Magento_Customer/js/customer-data'
], function (Component, customerData) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Vendor_FreeShipping/progress'
        },

        initialize: function () {
            this._super();
            this.progress = customerData.get('free-shipping-progress');

            return this;
        }
    });
});

customerData.get() devolve um observable do Knockout, então o template lê como progress().remaining e atualiza quando a section é recarregada. Se precisar reagir às mudanças no código, faça subscribe com this.progress.subscribe(callback).

O template e o setup com text/x-magento-init seguem o mesmo padrão de qualquer outro componente da loja. O artigo sobre UI components e noções básicas de Knockout cobre essa parte.

Erros comuns

Usar cacheable="false" em vez disso

Marcar um block com cacheable="false" no layout XML desativa o full page cache da página inteira, não só do block. Em um handle default.xml, desativa para a loja toda. Se uma página tem dados pessoais, mova esses dados para uma section e mantenha a página cacheável. O artigo sobre full page cache com Varnish mostra como conferir a taxa de hits depois da mudança.

Colocar dados do cliente em um block e torcer para dar certo

Código antigo às vezes lê a sessão do cliente dentro de um block que acaba no HTML em cache. Numa página em cache, a sessão nesse block está vazia, porque o Magento limpa os dados do cliente da sessão durante a renderização de páginas cacheáveis. O block mostra conteúdo de visitante para todo mundo, ou pior, o conteúdo de quem aqueceu o cache primeiro, se os dados vieram de outro lugar.

Forçar um reload em toda página

Chamar customerData.reload(['cart'], true) em todo carregamento de página anula o cache do local storage e adiciona uma requisição sem cache a cada visualização. Faça o reload em resposta a ações, não no carregamento da página.

Sections grandes

Os dados da section são serializados no local storage e enviados para toda página. Não devolva coleções completas de produtos nem trechos de HTML. Devolva o mínimo que o componente precisa para renderizar.

Usar uma section para dados que não são privados

O erro oposto também acontece. Uma loja carrega uma lista de banners ou um aviso para a loja toda por uma section customizada porque pareceu fácil. Esses dados são iguais para todos os visitantes, então pertencem ao HTML em cache, ou a um block com uma cache key adequada. Movê-los para uma section adiciona uma requisição sem cache e um flash de conteúdo vazio sem benefício nenhum. Sections são para dados que mudam por visitante, nada além disso.

Debug

  • Veja na aba Network as requisições para customer/section/load e confira qual parâmetro sections elas enviam.
  • Inspecione mage-cache-storage no local storage para ver os valores atuais e os timestamps.
  • No console, require('Magento_Customer/js/customer-data').get('cart')() imprime a section cart atual.
  • Para testar de um estado limpo, limpe o local storage do domínio e recarregue.

Para saber mais sobre qual cache guarda o quê, veja cache types do Magento 2 explicados.

FAQ

Por que o minicart mostra itens velhos depois de uma ação?

O controller que alterou o carrinho não está mapeado para a section cart em nenhum sections.xml, então o navegador mantém a cópia antiga no local storage. Adicione uma entrada de action para o caminho desse controller.

Os dados da section são seguros?

Eles são carregados só para a sessão atual e guardados no próprio navegador do visitante. Mesmo assim, trate como visível para o usuário e não inclua nada que ele não deveria ver, como dados internos de custo.

Posso usar sections para preço de cliente logado?

Preços por grupo de cliente são tratados pelo Magento pelo customer context no full page cache, que cria variantes em cache separadas por grupo. Use sections para dados que são realmente por cliente, não por grupo.

As customer data sections existem no Hyvä?

Sim. O Hyvä mantém as sections no servidor e o customer/section/load, e troca o cliente em RequireJS por um JavaScript leve próprio que dispara um evento quando o conteúdo privado carrega.

Atendo como freelancer em Magento e Adobe Commerce, incluindo correções de full page cache e performance. Se precisar de ajuda com isso, fale comigo.

Artigos relacionados