Buzeli
buzeliSoluções Digitais
WordPress

O carrinho que esvaziava sozinho: o prefixo de cookie no CloudFront que zerou as vendas do WooCommerce

Publicado em 18 de junho de 2026

Ilustração de um carrinho de compras vazio e um cookie quebrado em uma rede de borda CDN

O sintoma: carrinho cheio no popup, vazio na página

Um e-commerce WooCommerce rodando atrás de CloudFront, com nginx + WAF em uma instância EC2 como origin. O cliente adicionava um produto: o mini-cart lateral, atualizado via AJAX, mostrava o item normalmente. Mas ao abrir /carrinho/ ou /finalizar-compra/, o WooCommerce respondia "Seu carrinho está vazio".

O impacto era direto na receita: nenhum pedido conseguia ser finalizado. Para uma loja transacional, isso não é um bug de UX — é a operação parada.

O diagnóstico parcial: o lugar certo, pela razão errada

O primeiro diagnóstico apontou para o cache do CloudFront — e estava certo em apontar para lá. Mas a leitura dos detalhes estava errada em dois pontos que mudavam totalmente a solução:

Cache policy errada: assumiu-se que a distribution usava a policy gerenciada Managed-CachingOptimized. Na veridade era a UseOriginCacheControlHeaders-QueryStrings — que respeita o Cache-Control que o origin envia. A diferença é crucial, como veremos.

Behaviors dedicados por path: a proposta foi criar behaviors separados para /carrinho/, /finalizar-compra/ e /minha-conta/, marcando-os como não-cacheáveis. Inviável: o plano tinha limite de 5 behaviors e todos já estavam em uso.

O cache do CloudFront estava no caminho do problema. Mas a solução não era criar mais behaviors — era fazer o origin falar a língua que a cache policy já estava ouvindo.

Causa raiz, camada 1: o origin não enviava Cache-Control

A policy UseOriginCacheControlHeaders respeita o header Cache-Control da resposta do origin para decidir o que cachear e por quanto tempo. O problema: o nginx não enviava Cache-Control em resposta nenhuma — nem para assets, nem para páginas. WordPress, por padrão, também não envia headers de cache adequados para um CDN.

Sem o header, o comportamento ficava indefinido: o CDN podia decidir cachear uma página dinâmica de carrinho, servindo a mesma resposta para usuários diferentes. Mas, sozinho, isso ainda não explicava o carrinho vazio. Faltava a segunda camada.

Causa raiz, camada 2: a CloudFront Function comia o cookie de sessão

Havia uma CloudFront Function rodando no evento viewer-request, cuja função era normalizar a cache key filtrando cookies — mantendo apenas os que importam para o cache e descartando o resto. A whitelist mantinha cookies com prefixo woocommerce_.

O detalhe que quebrava tudo: o cookie de sessão do WooCommerce não se chama woocommerce_session — ele é wp_woocommerce_session_HASH, com prefixo wp_. Ele não casava com a whitelist. Resultado: a Function removia o cookie HttpOnly de sessão antes da request chegar ao origin. Mesmo quando o nginx recebia a request, o WordPress não via o cookie de sessão e criava uma sessão nova e vazia. Por isso o carrinho "esvaziava" ao trocar de página.

Copiar
// CloudFront Function (viewer-request) — whitelist de cookies para a cache key

// ANTES: o cookie de sessão wp_woocommerce_session_ NÃO casava
function keepCookie(name) {
  return name.startsWith('woocommerce_');
}

// O cookie de sessão real era: wp_woocommerce_session_1a2b3c...  (prefixo wp_)
// → removido antes de chegar ao origin → WordPress recriava sessão vazia

O fix, camada 1: Cache-Control correto no nginx

No location / do server block do WAF, passamos a definir o Cache-Control explicitamente — com no-store para as rotas transacionais e bypass quando há cookie de sessão/login:

Copiar
# nginx (origin) — Cache-Control por rota + bypass por cookie

location / {
    proxy_hide_header Cache-Control;

    set $page_cache "public, s-maxage=3600, max-age=300";

    # Rotas transacionais nunca são cacheadas
    if ($request_uri ~* "^/(carrinho|finalizar-compra|minha-conta)") {
        set $page_cache "no-store, no-cache, must-revalidate";
    }

    # Qualquer sessão ativa (login ou carrinho) faz bypass
    if ($http_cookie ~* "wordpress_logged_in_|woocommerce_session_|woocommerce_cart_hash") {
        set $page_cache "no-store, no-cache, must-revalidate";
    }

    add_header Cache-Control $page_cache;
}

Para assets estáticos, o oposto — cache longo e imutável, removendo o Vary que atrapalha a eficiência do CDN:

Copiar
# nginx (origin) — assets estáticos

location ~* \.(css|js|jpg|jpeg|png|gif|svg|woff2?|ico)$ {
    proxy_hide_header Vary;
    proxy_hide_header Cache-Control;
    add_header Cache-Control "public, max-age=31536000, immutable";
}

Por que detecção por path E por cookie?

A detecção por path (/carrinho/, /finalizar-compra/, /minha-conta/) é a mais robusta: garante que essas rotas nunca sejam cacheadas, independente de o cliente ter um cookie ou não. A detecção por cookie cobre o resto — qualquer página vista por um usuário com sessão ativa não deve servir conteúdo cacheado de outra pessoa.

O fix, camada 2: incluir o prefixo wp_woocommerce_session_ na whitelist

A correção na CloudFront Function foi uma linha — adicionar o prefixo wp_woocommerce_session_ à whitelist de cookies preservados:

Copiar
// CloudFront Function (viewer-request) — whitelist corrigida

function keepCookie(name) {
  return name.startsWith('woocommerce_') ||
         name.startsWith('wp_woocommerce_session_');   // <-- o cookie de sessão real
}
Atenção: uma CloudFront Function publicada é global — afeta todas as distributions que a usam. A alteração precisa ser revisada e validada antes de ir para LIVE, não testada em produção no susto.

Validação

Teste de configuração e reload do nginx (rodando em container):

Copiar
sudo docker exec waf nginx -t
sudo docker exec waf nginx -s reload

Invalidação do CloudFront — primeiro um purge das rotas afetadas, para não esperar o TTL antigo expirar:

Copiar
aws cloudfront create-invalidation \
  --distribution-id EXXXXXXXXXXXXX \
  --paths "/carrinho/*" "/finalizar-compra/*" "/minha-conta/*"

O resultado, camada por camada:

Antes → Depois:

Copiar
/carrinho/ Cache-Control          : (nenhum)  ->  no-store, no-cache, must-revalidate
/finalizar-compra/ Cache-Control  : (nenhum)  ->  no-store, no-cache, must-revalidate
/minha-conta/ Cache-Control       : (nenhum)  ->  no-store, no-cache, must-revalidate
Páginas normais Cache-Control     : (nenhum)  ->  public, s-maxage=3600, max-age=300
Assets Cache-Control              : (nenhum)  ->  public, max-age=31536000, immutable
Cookie wp_woocommerce_session_    : removido  ->  passado ao origin
Carrinho funcional                : NÃO       ->  SIM

Fluxo completo validado de ponta a ponta: adicionar produto → /carrinho/ mostra os itens. Nos response headers, a confirmação: cache-control: no-store e x-cache: Miss from cloudfront nas rotas de carrinho.

Lições

1. O cookie de sessão do WooCommerce usa prefixo wp_. Ele é wp_woocommerce_session_HASH, não woocommerce_session. Qualquer CloudFront Function (ou regra de WAF/CDN) que filtre cookies por prefixo precisa incluir wp_woocommerce_session_ explicitamente.

2. Rotas transacionais devem ser no-store por path. /carrinho/, /finalizar-compra/ e /minha-conta/ nunca devem ser cacheadas. Detecção por path é mais robusta que por cookie — não depende de o cliente já ter uma sessão.

3. Dois problemas sobrepostos exigem dois fixes. Mesmo corrigindo o Cache-Control no origin, se a Function remove o cookie de sessão o WordPress recria sessão vazia. As duas camadas precisavam estar corretas ao mesmo tempo — corrigir só uma daria a falsa sensação de que o fix "não funcionou".

4. Limite de behaviors? Controle pelo origin. Quando o plano do CDN limita o número de behaviors, a saída não é criar behaviors dedicados por rota — é controlar o cache via headers Cache-Control no origin, que a cache policy correta já respeita.

Esse mesmo tema de cookie atrás de CDN já apareceu por aqui — em um loop de 401 invisível com auth_basic no wp-login. A diferença é que lá o problema era um header sendo exigido; aqui era um cookie sendo descartado.

Conclusão

Quando infra, CDN e aplicação interagem, o bug raramente mora em uma camada só. O carrinho vazio não era "culpa do WooCommerce" nem "culpa do CloudFront" isoladamente: era a soma de um origin silencioso (sem Cache-Control) com uma Function zelosa demais (descartando o cookie certo). O fix foi pequeno em cada ponta — mas só funcionou porque as duas pontas foram tratadas juntas.