Cloudflare Workers e WordPress: servir o WooCommerce na edge
O Cloudflare Workers executa JavaScript no limite da rede global da Cloudflare. O WordPress executa PHP num único servidor de origem. Pôr o Workers à frente do WordPress é a arquitetura que permite a uma loja WooCommerce herdar o desempenho da rede edge sem reescrever o CMS. Os compromissos são reais e merecem ser nomeados explicitamente.
Este artigo ancora-se no pilar serviço de WordPress headless e emparelha com a decisão de renderização ISR vs SSR e a economia do WordPress headless.
TL;DR
- O Workers trata do caminho de leitura, o WordPress trata das escritas e da autoria.
- O catálogo e os detalhes de produto do WooCommerce renderizam na edge; o carrinho e o checkout ficam na origem.
- A invalidação de cache é guiada por webhook, não pelo tempo.
- O Cloudflare Pages limita o ficheiro _redirects a 2000 regras, planeia em torno disso.
- O plano gratuito do Workers limita o bundle comprimido a 1 MB; o plano pago aumenta este valor.
O que é, de facto, o Cloudflare Workers
O Workers é um runtime de isolates V8 que corre JavaScript, TypeScript e WebAssembly dentro da rede edge da Cloudflare (a página de rede lista os centros de dados). Não é Node.js, não é um contentor Lambda e não é sítio para correr PHP. O WordPress fica na origem; o Worker é um reverse proxy em JS à frente dela.
Algumas características que moldam o que se pode ou não fazer com Workers à frente de WordPress:
- Sem PHP. Um Worker não executa
wp-load.php. Pode ir buscar a/wp-json/wp/v2/postse a/wp-json/wc/v3/products, transformar a resposta e devolver HTML ou JSON. A instalação WordPress trata de qualquer mutação autenticada, incluindo a callback do IfthenPay com a verificação de assinatura SHA-1 que, no fluxo normal, vive emwp-content/plugins/ifthenpay/. - O CPU é o orçamento que aperta primeiro. O plano gratuito permite cerca de 10 ms de CPU por pedido, o pago sobe perto dos 30 ms e o Unbound vai mais longe. Uma página de produto ISR servida do KV gasta praticamente nada; um template SSR que agrega quatro chamadas a
/wp-json/e gera o IVA OSS por país comunitário a partir docf-ipcountrycomeça a esfregar o tecto do plano gratuito. - Duas camadas de armazenamento, dois modelos de consistência. O Workers KV é eventualmente consistente: leituras locais rápidas, escritas demoram até cerca de um minuto a propagar e em rajada são limitadas. O D1 é SQLite na edge com consistência forte. Para WooCommerce em PT, o KV serve para listagens de catálogo cacheadas; o stock vivo do Multibanco com referência ainda em backlog (a fila de pagamentos por confirmar entre o gateway e o
woocommerce_thankyou) tem de ficar na origem ou em Durable Objects. - O tamanho do bundle conta. O script comprimido sai a 1 MB no plano gratuito e a 10 MB no pago. Uma app Astro ou Next.js completa, com bundles para PT-PT e PT-BR e o cliente REST do WooCommerce, exige o plano pago.
- Produtos vizinhos resolvem formas semelhantes. Cloudflare Pages Functions corre no mesmo runtime e é a casa ergonomicamente correcta para uma app Astro ou Next.js. Vercel Edge Functions e AWS Lambda@Edge resolvem a mesma forma com modelos de custo diferentes. O Workers ganha quando o WordPress já vive atrás do DNS Cloudflare e a equipa quer uma só plataforma para cache, WAF e renderização.
Código escrito contra APIs da Web Platform (Fetch, Request, Response, Streams, Web Crypto) corre sem alterações. Código que dependa da biblioteca padrão do Node, módulos nativos ou acesso ao filesystem, não.
A arquitetura em duas camadas
Numa configuração Workers + WordPress, dois sistemas partilham a responsabilidade:
Origem WordPress. Autoria (Block Editor, REST API, WP-CLI), operações de escrita (criar, atualizar, eliminar posts; criação de encomendas; registo de utilizadores) e fonte da verdade para conteúdo e dados de produto.
Edge Cloudflare Workers. Renderização do caminho de leitura (geração de HTML), agregação de API (combinar vários endpoints REST do WordPress numa só resposta para o front), edge caching (por rota, por tag) e preocupações transversais (geo-routing, variantes A/B, reescrita de cabeçalhos).
A fronteira é nítida. Tudo o que altera estado vive na origem WordPress. Tudo o que lê, renderiza ou agrega pode viver no Workers. Carrinho e checkout sentam-se na linha de fronteira porque são stateful mas predominantemente de leitura.
Padrões concretos do WooCommerce que migram para Workers
Padrões de Workers à frente de WooCommerce que entregámos ou vimos a entregar de forma limpa em lojas portuguesas:
Cache edge para leituras /wp-json/. Uma rota Worker faz match a /wp-json/wc/v3/products* e serve de cache quando a chave (URL mais um pequeno conjunto de cabeçalhos vary) está fresca. Endpoints sensíveis a stock como /wp-json/wc/v3/products/<id> recebem TTL mais curto e invalidação por webhook na acção woocommerce_product_set_stock. A origem PHP deixa de tratar o fan-out de listagens vindo de exportadores de feeds para o KuantoKusta, scrapers de preços e crawlers automatizados.
Cache da página com consciência do carrinho. A página de catálogo é cacheada, mas o fragmento do mini-carrinho não. O Worker lê o cookie wp_woocommerce_session_*, vai buscar o fragmento à origem e costura a resposta. O HTML do catálogo continua cacheável entre todos os anónimos; o fragmento do carrinho é por sessão e nunca é cacheado. O mesmo padrão serve para mostrar a referência MB Way gerada na origem dentro de um esqueleto público que continua estático.
Transformação de imagem na edge. Imagens de produto são carregadas para a media library WordPress em resolução total. Uma rota Worker à frente de /wp-content/uploads/ redimensiona, recodifica para AVIF e cacheia na edge. A origem deixa de chamar Imagick em cada pedido sem cache, que numa loja WooCommerce em Black Friday é uma das fontes de CPU mais barulhentas.
Redireccionamentos e variantes geo. O Worker lê cf-ipcountry e separa visitantes de Portugal continental dos das ilhas para mostrar o tracker dos CTT correcto na página de seguimento, ou recalcula o IVA OSS para um visitante de Espanha B2C sem ir ao PHP. A mesma lógica em WordPress obriga a passar por wp-load.php para ler is_eu_visitor() de um plugin.
Regras WAF para além do WAF Pro. Bloquear POST a /xmlrpc.php, limitar consultas /?s= por ASN único, dropar pedidos que tragam wp-config no path, ou validar a assinatura HMAC de callbacks IfthenPay antes de chegarem ao PHP. O Workers dá uma camada programável sobre o WAF gerido sem pagar slots de regras Enterprise.
O que fica na origem WordPress: gravações do Block Editor, criação de encomendas a partir do checkout autenticado, callbacks dos gateways de pagamento (IfthenPay, MB Way, Multibanco), sessões de admin e o fan-out de webhooks que dispara invalidação de cache no Worker. O servidor PHP escala ao volume editorial e ao volume de encomendas, não ao volume de tráfego anónimo no catálogo.
Onde traçar a fronteira
Três categorias de rota, três regras de renderização diferentes:
Estático ou ISR na edge. Páginas de marketing, posts de blogue, arquivos de categorias, páginas de detalhe de produto. Cacheadas agressivamente com invalidação por webhook.
SSR na edge sem cache. Carrinho, conta, dashboards, páginas de checkout que são personalizadas e movidas por sessão. O Workers executa a renderização a cada pedido; o WordPress fornece os dados via REST.
Apenas origem, sem Worker. WP Admin (/wp-admin/), o Block Editor e os endpoints REST do core WordPress usados internamente para autoria. A configuração de rotas do Worker exclui-os explicitamente da camada edge.
O erro comum é tentar mover o carrinho para a cache edge. O carrinho é estado por utilizador; cacheá-lo entre utilizadores é um incidente de segurança à espera de acontecer. SSR na edge é o modelo certo: o Worker renderiza o HTML para o carrinho desse utilizador, mas não cacheia o resultado.
Invalidação de cache, a peça de carga
ISR na edge só é correcto quando a invalidação é correcta. O padrão que funciona:
Marcar com tags cada resposta cacheada. Cada página renderizada é marcada com os IDs de post, IDs de termo e IDs de produto do WordPress que referencia. A cache do Cloudflare Workers suporta purga baseada em tags via API de cache.
Webhook em cada escrita do WordPress. Publicação, mudança de slug, eliminação de post, atualização de stock, mudança de preço. Cada um dispara um webhook para uma rota Workers que traduz “post 8421 mudou” em “purga a tag wp-post-8421” via API da Cloudflare.
Revalidação por tempo apenas como salvaguarda. Uma janela stale-while-revalidate de 1 hora cobre falhas de entrega de webhooks. Não é o mecanismo principal. Um site que revalide a cada 60 segundos reconstrói cada página 60 vezes por hora; em 5000 páginas isso são 300000 renderizações desnecessárias.
A ordem importa: invalidar e depois escrever. Quando uma publicação WordPress dispara o webhook, o Worker invalida a tag e o pedido seguinte reconstrói a página a partir da origem agora atual. Se a ordem for invertida, uma renderização antiga entra na cache e fica até à próxima invalidação.
Limites a ter em conta no planeamento
Limites de plataforma documentados pela Cloudflare:
- Limite de
_redirectsno Cloudflare Pages: 2000 regras. Acima disso, os deploys falham. Acompanhamos isto na pipeline de build e estamos atualmente em 1600. - Tamanho do bundle do Worker: 1 MB comprimido no plano gratuito, mais elevado nos planos pagos. Rotas que entregam aplicações Astro ou Next.js completas precisam do plano pago.
- Tempo de CPU por pedido: 10 ms no plano gratuito, 50 ms ou mais nos planos pagos. SSR com lógica de template pesada ultrapassa rapidamente o tecto do plano gratuito; respostas em cache via ISR consomem praticamente nada de CPU.
- Subpedidos por pedido: 50 no plano gratuito, mais nos pagos. Agregação REST do WordPress que se ramifica por vários endpoints encontra este limite em páginas complexas.
A resposta arquitetónica padrão: desenhar para o plano pago, entregar no plano pago, tratar o plano gratuito como sandbox de desenvolvimento.
Compatibilidade com Astro e Next.js
Segundo o opinião actual do Tech Radar, tanto Astro 5+ como Next.js 15 trazem adaptadores oficiais Cloudflare. O adaptador do Astro compila as rotas estáticas + SSR para Workers; o adaptador do Next.js faz o mesmo com o App Router. Ambos produzem um artefacto de deploy que corre em Cloudflare Pages com Workers por baixo.
A escolha entre eles está coberta na matriz de decisão Next.js vs Astro. Para uma loja WooCommerce orientada a conteúdo, o Astro é normalmente a escolha por defeito porque o modelo static-first mapeia directamente para o catálogo. Para uma configuração mais voltada a aplicação (conta, dashboard, personalização complexa), o modelo de server components do Next.js tem a vantagem ergonómica.
De qualquer forma, a origem WordPress fica inalterada. Workers + WordPress é um padrão arquitetónico, não uma escolha de framework.
Formas de falha que vale a pena conhecer antes de entregar
Algumas maneiras concretas em que vimos uma instalação Workers + WordPress partir-se ou surpreender a equipa em lojas PT:
Limitação de escritas KV em invalidação em rajada. Uma importação em massa que toca 5000 SKUs vindas do ERP dispara 5000 webhooks woocommerce_update_product. O Worker traduz cada um numa escrita KV por chave de SKU. O throughput de escrita do KV é limitado por namespace, as escritas ficam em fila e a cache fica obsoleta durante minutos, com referências Multibanco a apontar para um stock que já não existe. A correcção é fazer debounce no lado WordPress e batch das invalidações, ou trocar o mapa por SKU por uma única chamada de purga por tag contra a API de cache da Cloudflare.
Cold-start vs warm-start em locales de baixo tráfego. Um isolate V8 reutilizado entre pedidos quase não custa arranque. O primeiro pedido a uma região após uma janela longa de inactividade paga 5 a 50 ms extra enquanto o isolate arranca e o script é parseado. Para a rota PT-PT de uma loja que sirva sobretudo Espanha, o p99 fica pior do que o p50. Monitores sintéticos de Lisboa e do Porto distribuem o problema; o RUM revela-o.
Tecto de 1 MB no plano gratuito. Uma app Next.js 15 completa, com adaptador Cloudflare, react-server-dom, bundles para PT-PT e PT-BR, calculadora de IVA OSS por país e cliente REST do WooCommerce, estoura facilmente 1 MB comprimido. O deploy falha no upload, não em runtime. Planeia-se para o pago (10 MB) ou divide-se a renderização entre vários scripts Worker.
Fan-out de subpedidos em páginas agregadas. Uma página de categoria que vai buscar o termo, os produtos, as categorias relacionadas, as breadcrumbs, o menu e ainda a tabela de portes CTT por código postal bate em seis endpoints /wp-json/. O plano gratuito limita os subpedidos a 50 por invocação Worker, o pago a 1000. Camadas de agregação que pré-compõem a resposta no lado WordPress, ou um endpoint GraphQL único via WPGraphQL, mantêm a contagem previsível.
Falhas de entrega de webhooks deixando cache obsoleta. O WordPress dispara o webhook na acção shutdown; se o worker PHP-FPM expira ou se a rede falha entre a confirmação do callback IfthenPay e o do_action, o webhook nunca chega ao Worker. Um stale-while-revalidate de 1 hora baseado em tags apanha isto; sem a salvaguarda, a renderização obsoleta persiste até à próxima purga manual.
As fases de migração (auditoria, scaffolding REST e webhooks, build e cutover, observabilidade) estão documentadas no schema howTo no topo deste artigo. Corremo-las dentro do nosso serviço de WordPress headless. O preço é individual porque TTFB e TTI dependem da carga de plugins da origem, do framework escolhido e do perfil real de tráfego; o scaffold de benchmark documenta o protocolo de medição a que nos vinculamos.
Onde isto se encaixa
Este artigo pilar ancora-se no cluster serviço de WordPress headless. Para a escolha de framework, ver a matriz de decisão Next.js vs Astro. Para a escolha de renderização por rota, ver a decisão ISR vs SSR. Para riscos de migração SEO, ver a checklist de padrões SEO de WordPress headless. Para o enquadramento de custos, ver a economia do WordPress headless 2026.
