Em 2026, a diferença de performance entre um site WordPress “padrão” e um site WordPress “empresarial” é definida pela inteligência de cache. Enquanto o caching básico de páginas era suficiente há uma década, as aplicações web modernas requerem estratégias de caching multi-camada que compreendem o que colocar em cache, onde o fazer e precisamente quando invalidar.
Há dez anos, o caching era simples: guardava-se uma página como ficheiro HTML e servia-se até expirar. Em 2026, essa abordagem tudo-ou-nada já não é suficiente. Os websites modernos são entidades híbridas: parte estática, parte dinâmica, servidas globalmente a utilizadores com diferentes velocidades de ligação e dispositivos. Para atingir 100/100 em Core Web Vitals enquanto serve conteúdo personalizado, precisa de uma estratégia que otimize cada camada da stack de entrega.
A arquitetura prática é feita de camadas: cache do browser, edge da CDN, cache de página completa, cache de fragmentos, object cache e higiene da base de dados, a trabalhar em conjunto em vez de competirem entre si.
Como funciona o edge caching em três camadas no WordPress
Em 2026, o seu servidor de origem quase nunca recebe diretamente o pedido de um visitante. As implementações modernas de WordPress seguem uma estratégia de caching em três camadas que distribui o conteúdo por todo o mundo.
Camada 1: cache estática no edge (CDN)
HTML, imagens, CSS e JavaScript ficam guardados no edge da rede através de serviços como Cloudflare, Bunny.net ou Fastly. Isto proporciona o Time to First Byte (TTFB) abaixo de 50 ms que os utilizadores esperam.
Configurações principais:
- Cache Everything: colocar HTML em cache no edge, não apenas ativos estáticos
- Edge TTL: definir tempos de vida adequados (1 hora para conteúdo dinâmico, 1 ano para estático)
- Browser TTL: controlar os cabeçalhos de cache do lado do cliente
- Always Online: servir conteúdo desatualizado se a origem estiver inacessível
Exemplo de Page Rules no Cloudflare:
*example.com/*
Cache Level: Cache Everything
Edge Cache TTL: 2 hours
Browser Cache TTL: 30 minutes
Always Online: OnCamada 2: caching de respostas da API
Em configurações WordPress headless ou desacopladas, as respostas da API ficam em cache separadamente do HTML:
- Endpoints da REST API em cache com TTL curto (5 a 15 minutos)
- Consultas GraphQL em cache conforme a complexidade e a volatilidade dos dados
- Purga de cache acionada por webhooks para atualizações imediatas
Benefícios:
- Reduz a carga na base de dados para frontends baseados em JavaScript
- Permite experiências mais rápidas em aplicações móveis
- Suporta funcionalidades em tempo real com fallbacks em cache
Camada 3: cache locking e proteção contra thundering herd
Quando um item em cache expira, as configurações tradicionais deixam vários pedidos simultâneos chegar ao servidor de origem. Em 2026, implementamos cache locking:
- Apenas o primeiro pedido regenera o conteúdo em cache
- Os pedidos seguintes recebem uma versão ligeiramente desatualizada
- Quando a regeneração termina, todos os utilizadores recebem conteúdo atualizado
Implementação com Cloudflare Workers:
// Cache locking pattern
async function handleRequest(request) {
const cache = caches.default;
const cacheKey = new Request(request.url);
let response = await cache.match(cacheKey);
if (!response) {
// Check for regeneration lock
const lockKey = `lock:${request.url}`;
const lock = await CACHE_LOCKS.get(lockKey);
if (lock) {
// Serve stale while regenerating
response = await cache.match(`${cacheKey}:stale`);
} else {
// Acquire lock and regenerate
await CACHE_LOCKS.put(lockKey, 'locked', {expirationTtl: 30});
response = await fetchOrigin(request);
await cache.put(cacheKey, response.clone());
await cache.put(`${cacheKey}:stale`, response.clone());
}
}
return response;
}Cache de página completa com Nginx FastCGI e Varnish
As redes edge entregam rapidamente ativos estáticos e HTML público, mas a origem continua a precisar de uma cache de página completa eficiente antes de o PHP começar a executar. A cache FastCGI do Nginx guarda as respostas HTML geradas em zonas de memória partilhada e no disco local, e os visitantes anónimos não passam pelos workers do PHP-FPM:
# FastCGI cache configuration in nginx.conf
fastcgi_cache_path /var/run/nginx-cache levels=1:2 keys_zone=WORDPRESS:256m inactive=1d max_size=4g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# Purge location using fastcgi_cache_purge
location ~ /purge(/.*) {
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
fastcgi_cache_purge WORDPRESS "$scheme$request_method$host$1";
}Quando um editor atualiza conteúdo, um plugin de purga envia um pedido HTTP PURGE para o endpoint local do Nginx e a entrada dessa chave de cache é removida. A documentação do módulo FastCGI do nginx indica que os nomes dos ficheiros de cache são o MD5 da chave e apresenta fastcgi_cache_purge como parte da subscrição comercial, por isso as builds open source do Nginx usam o módulo de terceiros ngx_cache_purge para o mesmo trabalho. A mesma página documenta duas diretivas que vale a pena ativar em qualquer origem WordPress com muito tráfego: fastcgi_cache_lock, que permite que apenas um pedido de cada vez preencha um novo elemento da cache, e fastcgi_cache_use_stale, que serve uma resposta desatualizada quando o backend FastCGI falha. Em ambientes com vários servidores e Varnish, pedidos PURGE ou expressões BAN limpam entradas por padrão de URL ou por cabeçalhos de tags emitidos por plugins WordPress.
Como fazer purge da cache WordPress por tags
O maior problema do caching empresarial sempre foi o “problema da purga”. Corrige uma gralha na página inicial e toda a cache do site é apagada, o que provoca uma quebra de performance e sobrecarga no servidor de origem.
Em 2026, resolvemos isto com cache tagging.
Como funciona o cache tagging
Em vez de tratar o conteúdo em cache como ficheiros isolados, marcamos cada item em cache com metadados sobre as suas dependências:
Homepage: tags=["home", "post_123", "post_124", "category_news"]
Blog Post: tags=["post_125", "category_tutorials", "author_john"]
Category Page: tags=["category_tutorials", "post_125", "post_126"]Quando atualiza o “Artigo A”, apenas os itens com a tag post_a são purgados:
- O próprio artigo
- As páginas de arquivo da categoria
- Os widgets de artigos relacionados
- As páginas de arquivo do autor
- A página inicial (se o artigo estiver em destaque)
O resto das suas 10.000 páginas continua em cache e rápido.
Implementar cache tags em WordPress
Com Cloudflare:
// Add cache tags to Cloudflare
function add_cache_tags_header() {
if (is_single()) {
$tags = [
'post_' . get_the_ID(),
'author_' . get_the_author_meta('ID'),
];
// Add category tags
$categories = get_the_category();
foreach ($categories as $cat) {
$tags[] = 'category_' . $cat->slug;
}
header('Cache-Tag: ' . implode(',', $tags));
}
}
add_action('template_redirect', 'add_cache_tags_header');Purga por tag:
// Purge specific tags when post is updated
function purge_post_cache_tags($post_id) {
$tags = [
'post_' . $post_id,
'author_' . get_post_field('post_author', $post_id),
];
// Cloudflare API purge by tag
$api = new Cloudflare\API\Client($email, $api_key);
$api->zones()->cachePurgeTags($zone_id, $tags);
}
add_action('save_post', 'purge_post_cache_tags');Que cache tags purgar quando um artigo muda
Hierarquia de tags:
post_{id}: conteúdo de um artigo individualcategory_{slug}: páginas de arquivo de categoriaauthor_{id}: páginas de arquivo de autortaxonomy_{name}_{term}: páginas de taxonomias personalizadaswidget_{id}: conteúdo de widgets dinâmicosglobal: elementos de todo o site (cabeçalho, rodapé, menus)
Purga condicional:
// Only purge what changed
function smart_cache_purge($post_id) {
$post = get_post($post_id);
$tags = ['post_' . $post_id];
// If featured image changed, purge image-related caches
if (did_post_thumbnail_change($post_id)) {
$tags[] = 'featured_images';
}
// If post status changed, purge archive listings
if ($post->post_status !== get_previous_post_status($post_id)) {
$tags[] = 'category_' . get_primary_category($post_id);
$tags[] = 'author_' . $post->post_author;
}
purge_cache_tags($tags);
}Redis object cache para WordPress em 2026
A base de dados é o estrangulamento do WordPress. Cada carregamento de página dispara dezenas de consultas SQL para obter artigos, metadados, opções e dados de utilizadores. O object caching guarda os resultados dessas consultas em memória e elimina pesquisas repetidas na base de dados.
Porque usar Redis 8+ em 2026
As notas de lançamento do Redis Open Source 8.0 (maio de 2025) indicam que o Redis Search, o JSON, as séries temporais e cinco estruturas de dados probabilísticas, antes módulos separados, passaram a fazer parte do próprio Redis. Para o WordPress, isto significa que um único servidor pode guardar o object cache e também os dados usados por funcionalidades de pesquisa ou de métricas:
Funcionalidades do Redis 8:
- RedisJSON: guardar e consultar objetos JSON complexos
- RedisSearch: pesquisa de texto completo dentro dos dados em cache
- RedisTimeSeries: acompanhar métricas de performance da cache
- Opções de persistência: RDB e AOF para durabilidade dos dados
Redis vs Memcached (comparação 2026):
| Característica | Redis 8+ | Memcached |
|---|---|---|
| Tipos de dados | 8+ (strings, hashes, listas, sets, sorted sets, JSON, streams) | 1 (strings) |
| Persistência | RDB + AOF | Nenhuma |
| Replicação | Master-slave + Cluster | Nenhuma |
| Eficiência de memória | Melhor com compressão | Boa |
| Invalidação por tags | Manual (por exemplo, conjuntos de chaves) | Implementação manual |
| Capacidade de consulta | RedisSearch | Nenhuma |
Drop-in Redis versus Memcached e o funcionamento do object cache
O core do WordPress oferece uma interface de cache substituível através de wp-content/object-cache.php. Sem um drop-in de object cache, o WordPress usa uma cache em memória que dura apenas um pedido PHP. Com um drop-in persistente (por exemplo, o instalado pelo plugin Redis Object Cache, ou um drop-in de Memcached), chamadas como wp_cache_set(), wp_cache_get(), wp_cache_delete() e wp_cache_add() são servidas pelo servidor Redis ou Memcached e sobrevivem entre pedidos. A referência de WP_Object_Cache descreve o comportamento por omissão sem rodeios: “por omissão, o object cache não é persistente”, pelo que os dados ficam “apenas em memória e apenas durante o pedido” (tradução própria).
Os programadores confundem frequentemente transients (set_transient(), get_transient()) com chamadas diretas ao object cache (wp_cache_set(), wp_cache_get()):
- Com um drop-in de object cache externo ativo,
set_transient()passa diretamente parawp_cache_set()no grupotransiente não toca na tabelawp_options. - Sem drop-in persistente, os transients são gravados em
wp_optionscomo linhas (_transient_*e_transient_timeout_*). No código deset_transient()do core, um transient guardado com expiração recebe uma linha de timeout e não é carregado automaticamente, enquanto um transient sem expiração é carregado em autoload em cada pedido, por isso os transients sem expiração fazem crescer as opções autoloaded sem ninguém dar por isso. - O manual da Transients API limita a chave a 172 caracteres e cita Ryan McCue: “os tempos de expiração dos transients são um tempo máximo. Não existe idade mínima” (tradução própria). O código tem de tratar sempre uma falha de cache, porque o Redis ou o Memcached podem remover o valor antes do fim do TTL.
- As chamadas diretas
wp_cache_*permitem grupos de cache próprios e invalidação granular.
// Storing complex computed data in a dedicated cache group
$cache_key = 'user_order_summary_' . $user_id;
$order_summary = wp_cache_get($cache_key, 'orders_analytics');
if (false === $order_summary) {
$order_summary = compute_heavy_user_order_metrics($user_id);
// Cache for 15 minutes in the orders_analytics group
wp_cache_set($cache_key, $order_summary, 'orders_analytics', 900);
}Grupos de cache e grupos não persistentes
Por omissão, um object cache externo mantém todos os grupos entre pedidos HTTP. No entanto, dados muito voláteis ou específicos de um pedido não devem ocupar a RAM externa nem pagar a latência de ida e volta. O core do WordPress oferece wp_cache_add_non_persistent_groups() para manter grupos específicos apenas na memória do processo PHP:
// Register non-persistent groups in an early hook or mu-plugin
function register_custom_cache_groups() {
// Keep temporary session flags in PHP process RAM only
wp_cache_add_non_persistent_groups([
'runtime_request_flags',
'transient_session_tokens',
]);
}
add_action('init', 'register_custom_cache_groups');Em redes multisite, wp_cache_add_global_groups() garante que as chaves desses grupos são partilhadas entre os sites da rede, em vez de receberem um prefixo por site.
Implementação do object cache em WordPress
Configuração básica do Redis (wp-config.php):
// Enable object cache
define('WP_CACHE', true);
// Redis settings
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_DATABASE', 0);
define('WP_REDIS_TIMEOUT', 1);
define('WP_REDIS_READ_TIMEOUT', 1);
// Advanced settings
define('WP_REDIS_DISABLE_METRICS', false);
define('WP_REDIS_DISABLE_BANNERS', true);
define('WP_REDIS_PREFIX', 'wp_');Estratégia de micro-cache: Mesmo entradas de cache de curta duração trazem benefícios grandes:
// Cache recent comments for 30 seconds
function get_cached_recent_comments($limit = 5) {
$cache_key = 'recent_comments_' . $limit;
$comments = wp_cache_get($cache_key);
if (false === $comments) {
$comments = get_comments([
'number' => $limit,
'status' => 'approve',
]);
wp_cache_set($cache_key, $comments, '', 30); // 30 seconds
}
return $comments;
}Object cache relacional: As implementações modernas compreendem as relações entre objetos:
// Automatic relationship caching
class RelationalObjectCache {
public function get_post_with_meta($post_id) {
$cache_key = "post_with_meta:{$post_id}";
$data = wp_cache_get($cache_key);
if (false === $data) {
$post = get_post($post_id);
$meta = get_post_meta($post_id);
$author = get_userdata($post->post_author);
$data = [
'post' => $post,
'meta' => $meta,
'author' => $author,
];
// Cache with relationship tags
wp_cache_set($cache_key, $data, '', 3600);
wp_cache_add_relationship($cache_key, "post:{$post_id}");
wp_cache_add_relationship($cache_key, "user:{$post->post_author}");
}
return $data;
}
}Monitorização e otimização do Redis
Monitorizar a taxa de acertos da cache:
# Redis CLI commands for monitoring
redis-cli info stats | grep keyspace
redis-cli info memory
redis-cli slowlog get 10Integração com o painel do WordPress:
// Add cache stats to admin bar
function add_cache_stats_to_admin_bar($wp_admin_bar) {
$stats = wp_cache_get_stats();
$hit_rate = ($stats['hits'] / ($stats['hits'] + $stats['misses'])) * 100;
$wp_admin_bar->add_node([
'id' => 'cache_stats',
'title' => sprintf('Cache: %.1f%% hit rate', $hit_rate),
'href' => admin_url('tools.php?page=redis-cache'),
]);
}Como fazer cache de conteúdo dinâmico no WordPress com ESI
Como se coloca em cache uma página que diz “Olá, [nome do utilizador]” ou mostra o total de um carrinho de compras? Em 2026, usamos Edge Side Includes (ESI) ou hidratação do lado do cliente para combinar conteúdo estático e dinâmico.
Porque é que a cache de página completa quebra a personalização
A cache de página completa tradicional quebra a personalização:
- As saudações ao utilizador tornam-se estáticas (“Olá, visitante” para todos)
- Os carrinhos de compras mostram estados desatualizados ou vazios
- A barra de administração aparece a todos os utilizadores
- Os preços dinâmicos não são atualizados
Como o fragment caching preenche marcadores no edge
A página principal fica 100% em cache como HTML estático. Os elementos dinâmicos são marcadores preenchidos no edge ou através de JavaScript:
Implementação ESI:
<!-- Static cached content -->
<h1>Welcome to Our Store</h1>
<!-- Dynamic fragment -->
<esi:include src="/esi/cart-count.php" />
<!-- Static content continues -->
<p>Check out our latest products...</p>
<!-- Personalized greeting -->
<esi:include src="/esi/user-greeting.php" />Hidratação do lado do cliente (alternativa):
<!-- Static placeholder -->
<div id="cart-count" data-dynamic-url="/api/cart-count">
<span class="placeholder">--</span>
</div>
<script>
// Hydrate on page load
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-dynamic-url]').forEach(el => {
fetch(el.dataset.dynamicUrl)
.then(r => r.text())
.then(html => el.innerHTML = html);
});
});
</script>Implementação de fragment caching em WordPress
Varnish ESI com WordPress:
// Mark dynamic content with ESI tags
function esi_dynamic_fragment($content, $callback, $args = []) {
$esi_url = add_query_arg([
'esi_action' => $callback,
'esi_args' => base64_encode(json_encode($args)),
], site_url('/esi-endpoint/'));
return "<esi:include src=\"{$esi_url}\" />";
}
// Usage in templates
echo esi_dynamic_fragment('user_greeting', ['user_id' => get_current_user_id()]);Contornar a cache de fragmentos do carrinho e do checkout no WooCommerce:
Numa loja WooCommerce padrão, wc-ajax=get_refreshed_fragments é chamado em cada carregamento de página sem cache para atualizar totais e número de artigos do carrinho. Cada um desses pedidos executa PHP na origem. Retirar o script de fragmentos das páginas que não mostram carrinho e substituí-lo por ESI ou armazenamento no cliente onde o carrinho aparece elimina esse pedido:
// Dequeue cart fragments on non-shop and static content pages
function bypass_woocommerce_cart_fragments() {
if (is_front_page() || is_single() || is_page()) {
if (!is_woocommerce() && !is_cart() && !is_checkout()) {
wp_dequeue_script('wc-cart-fragments');
}
}
}
add_action('wp_enqueue_scripts', 'bypass_woocommerce_cart_fragments', 20);Nas páginas de checkout, carrinho e conta, a cache de página tem de ser totalmente contornada no edge e no Nginx, com base na inspeção de cookies:
- Quando existe o cookie
woocommerce_items_in_cart, as caches edge não procuram a página em cache e encaminham o pedido diretamente para a origem. - Sessões sensíveis (
woocommerce_cart_hash,wp_woocommerce_session_) devolvem de imediato os cabeçalhosCache-Control: private, no-cache, no-store, must-revalidate. - Os fragmentos do carrinho dentro de páginas de catálogo em cache são atualizados através de
localStorageesessionStoragedo browser, que atualizam o mini-carrinho no DOM sem acionar processos PHP na origem.
Os fragmentos do carrinho e as exclusões do checkout fazem parte do trabalho de latência da loja, não apenas da configuração de ESI: veja otimização de performance WooCommerce 2026.
Como usar a Speculation Rules API para prefetching no WordPress
Em 2026, o caching também antecipa o que vai acontecer, além de guardar o que já aconteceu. Com a Speculation Rules API, o seu site WordPress pode prever e pré-carregar conteúdo antes de o utilizador clicar.
Como funciona o prefetching especulativo
A definição eagerness decide quando o browser atua. Segundo a documentação de prerender do Chrome, no desktop eager especula depois de o ponteiro ficar 10 milissegundos sobre um link, moderate depois de 200 milissegundos (ou no pointerdown, se acontecer antes) e conservative no momento em que o ponteiro ou o dedo pressiona o link. Com prerender, o browser renderiza a página de destino em segundo plano, e o clique seguinte parece instantâneo.
O WordPress 6.8 inclui isto no core. A nota de desenvolvimento sobre speculative loading explica que a configuração por omissão resulta no modo prefetch com eagerness conservative, que a funcionalidade fica desligada para utilizadores com sessão iniciada ou quando os permalinks amigáveis estão desativados, e que os URLs com parâmetros de consulta são excluídos. O filtro wp_speculation_rules_configuration altera o modo e a eagerness, e wp_speculation_rules_href_exclude_paths acrescenta exclusões, como os caminhos do carrinho e do checkout.
Implementação:
<script type="speculationrules">
{
"prerender": [{
"source": "list",
"urls": [
"/about/",
"/services/",
"/contact/"
]
}, {
"source": "document",
"where": {
"href_matches": "/blog/*",
"selector_matches": "a[rel='prerender']"
}
}]
}
</script>Como adicionar speculation rules no WordPress com PHP
Prefetching com base no conteúdo:
// Add speculation rules based on current page
function add_speculation_rules() {
if (!is_singular('post')) {
return;
}
// Get related posts for prefetching
$related = get_related_posts(get_the_ID(), 3);
$urls = array_map(function($post) {
return get_permalink($post->ID);
}, $related);
// Add next/prev posts
if ($next = get_next_post()) {
$urls[] = get_permalink($next->ID);
}
$rules = [
'prerender' => [[
'source' => 'list',
'urls' => $urls,
]],
];
echo '<script type="speculationrules">' .
json_encode($rules) .
'</script>';
}
add_action('wp_head', 'add_speculation_rules');Prefetching ao passar o rato (alternativa):
// Prefetch on link hover
let prefetchTimeout;
document.addEventListener('mouseover', (e) => {
const link = e.target.closest('a');
if (!link || link.hostname !== location.hostname) return;
prefetchTimeout = setTimeout(() => {
const prefetchLink = document.createElement('link');
prefetchLink.rel = 'prefetch';
prefetchLink.href = link.href;
document.head.appendChild(prefetchLink);
}, 100);
});
document.addEventListener('mouseout', () => {
clearTimeout(prefetchTimeout);
});Como funciona o stale-while-revalidate no cabeçalho Cache-Control
Os utilizadores detestam indicadores de carregamento. O SWR permite ao browser servir de imediato conteúdo “desatualizado” (ligeiramente antigo) enquanto obtém a versão atualizada em segundo plano.
Como funciona o SWR
- O utilizador pede uma página
- O browser verifica a cache
- Se existir conteúdo em cache (mesmo expirado), é servido de imediato
- Em segundo plano, é obtido conteúdo atualizado da origem
- A cache é atualizada e, opcionalmente, o conteúdo da página é refrescado
Cabeçalho Cache-Control:
Cache-Control: max-age=60, stale-while-revalidate=300Isto significa:
- O conteúdo é considerado atualizado durante 60 segundos
- Depois de 60 segundos, é servido conteúdo desatualizado até 300 segundos enquanto se revalida
- Depois de 360 segundos no total, é exigido conteúdo atualizado
Como implementar SWR no WordPress
Implementação com Service Worker:
// service-worker.js
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// Return cached version immediately (even if stale)
const fetchPromise = fetch(event.request).then((networkResponse) => {
caches.open('dynamic').then((cache) => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});
// Return stale or wait for fresh
return response || fetchPromise;
})
);
});Configuração SWR para Cloudflare:
// Add SWR headers to dynamic content
function add_swr_headers($headers) {
if (is_singular() && !is_user_logged_in()) {
$headers['Cache-Control'] = 'public, max-age=60, stale-while-revalidate=300';
}
return $headers;
}
add_filter('wp_headers', 'add_swr_headers');Que diretivas Cache-Control as páginas WordPress precisam no edge
O stale-while-revalidate é uma diretiva entre várias, e as outras decidem se uma página pode sequer ficar em cache. A referência de Cache-Control da MDN define as que importam para o WordPress:
| Diretiva | Definição da MDN (resumo) | Uso no WordPress |
|---|---|---|
s-maxage | Validade numa cache partilhada; aí sobrepõe-se a max-age e a Expires | TTL longo no edge para HTML anónimo, com max-age curto no browser |
stale-while-revalidate | A cache pode reutilizar uma resposta desatualizada enquanto a revalida | Páginas de arquivo e artigos que mudam algumas vezes por dia |
stale-if-error | A cache pode reutilizar uma resposta desatualizada quando a origem devolve 500, 502, 503 ou 504 | Mantém as páginas online durante uma falha do PHP-FPM ou da base de dados |
private | Só uma cache privada, como a do browser, pode guardar a resposta | Páginas de conta, vistas com sessão iniciada |
no-store | Nenhuma cache pode guardar a resposta | Carrinho, checkout, reposição de palavra-passe |
Dois detalhes da documentação de cache-control do Cloudflare mudam a forma como estas diretivas se combinam. Primeiro, o Cloudflare trata s-maxage como uma regra segundo a qual uma cache partilhada tem de revalidar com a origem antes de servir conteúdo desatualizado, por isso s-maxage desativa aí o stale-while-revalidate. Se quer revalidação em segundo plano no edge do Cloudflare, envie max-age com stale-while-revalidate e controle o TTL do edge com uma Cache Rule. Segundo, com o Origin Cache Control ativo e o nível de cache por omissão, uma resposta com Set-Cookie não fica em cache. O WordPress define cookies para utilizadores com sessão iniciada e o WooCommerce define-os para carrinhos, por isso um Set-Cookie perdido numa página pública desliga a cache do edge dessa página sem aviso.
O ponto de partida também conta. A página de comportamento de cache por omissão do Cloudflare afirma: “a CDN do Cloudflare não coloca HTML nem JSON em cache por omissão” (tradução própria). A cache funciona pela extensão do ficheiro, não pelo tipo MIME. A cache de página completa no edge para WordPress precisa por isso sempre de uma Cache Rule explícita que torne o HTML elegível, com condições de bypass para wp-admin, wp-login.php, a REST API quando devolve dados privados e os cookies wordpress_logged_in_ e woocommerce_items_in_cart.
Como monitorizar e fazer debugging da cache WordPress
Uma cache eficaz exige visibilidade sobre o que está a acontecer. Em 2026, usamos ferramentas de monitorização para garantir que a cache funciona como previsto.
Como adicionar cabeçalhos de debug X-Cache-Status
Adicione cabeçalhos de debug para acompanhar o comportamento da cache:
function add_cache_debug_headers() {
if (!current_user_can('manage_options')) {
return;
}
header('X-Cache-Status: ' . (did_cache_hit() ? 'HIT' : 'MISS'));
header('X-Cache-TTL: ' . get_cache_remaining_ttl());
header('X-Cache-Tags: ' . implode(', ', get_current_page_cache_tags()));
}
add_action('template_redirect', 'add_cache_debug_headers');Como registar acertos e falhas da cache no Redis TimeSeries
// Log cache metrics to Redis TimeSeries
function log_cache_metric($metric, $value) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// Add to time series
$redis->rawCommand('TS.ADD',
"cache:{$metric}",
'*',
$value,
'LABELS', 'site', get_site_url(), 'type', 'wordpress'
);
}
// Log on every request
add_action('wp', function() {
log_cache_metric('requests', 1);
if (did_cache_hit()) {
log_cache_metric('hits', 1);
} else {
log_cache_metric('misses', 1);
}
});Painel de monitorização da cache no WordPress
Crie um painel no administrador do WordPress para monitorizar a cache:
// Add cache health page
function add_cache_health_page() {
add_management_page(
'Cache Health',
'Cache Health',
'manage_options',
'cache-health',
'render_cache_health_page'
);
}
add_action('admin_menu', 'add_cache_health_page');
function render_cache_health_page() {
$stats = wp_cache_get_stats();
$hit_rate = $stats['hits'] / ($stats['hits'] + $stats['misses']) * 100;
?>
<div class="wrap">
<h1>Cache Health Dashboard</h1>
<div class="cache-stats">
<div class="stat-box">
<h3>Hit Rate</h3>
<div class="stat-value <?php echo $hit_rate > 90 ? 'good' : 'warning'; ?>">
<?php echo number_format($hit_rate, 1); ?>%
</div>
</div>
<div class="stat-box">
<h3>Cached Objects</h3>
<div class="stat-value"><?php echo number_format($stats['objects']); ?></div>
</div>
<div class="stat-box">
<h3>Memory Used</h3>
<div class="stat-value"><?php echo size_format($stats['memory']); ?></div>
</div>
</div>
</div>
<?php
}Como o Site Health do WordPress deteta a cache de página e o object cache
Desde o WordPress 6.1, Ferramentas, Diagnóstico executa dois testes que verificam o caching por dentro, e as regras desses testes são públicas no core. Lê-las explica porque é que um site com uma CDN a funcionar pode continuar a ver um aviso.
O teste de cache de página faz três pedidos à página inicial, regista os cabeçalhos de resposta e calcula o tempo de resposta mediano. Uma resposta conta como estando em cache quando traz um dos cabeçalhos listados em get_page_cache_headers(): cache-control com um max-age diferente de zero, uma data expires no futuro, um age positivo, last-modified, etag ou via, e ainda cabeçalhos de fornecedores como cf-cache-status, x-cache, x-cache-status, x-litespeed-cache, x-srcache-fetch-status com o valor HIT, ou x-varnish com dois identificadores de pedido. Um plugin de cache de página só é detetado quando três condições se verificam no código do core: wp-content/advanced-cache.php existe, a constante WP_CACHE é verdadeira e o filtro enable_loading_advanced_cache_dropin não foi desligado. As mesmas três condições decidem se wp-settings.php carrega advanced-cache.php, muito cedo, antes dos plugins.
O limiar do tempo de resposta é de 600 milissegundos por omissão. A função get_good_response_time_threshold() do core documenta que o valor se baseia nas orientações do web.dev para o time to first byte, e o filtro site_status_good_response_time_threshold altera-o. Um site atrás do Cloudflare cuja origem responde em 900 ms, porque o teste contorna o edge ou porque o HTML não está em cache no edge, será assinalado mesmo que os visitantes recebam respostas rápidas.
O teste de object cache persistente recomenda Redis ou Memcached quando qualquer uma destas condições se verifica:
- o site é uma rede multisite (sugestão sempre feita),
- mais de 500 opções em autoload, ou mais de 100.000 bytes de opções em autoload serializadas,
- 1.000 ou mais linhas nas tabelas de comentários, opções, artigos, termos ou utilizadores (o core lê estimativas de linhas do InnoDB em
information_schema, não contagens exatas).
Os limiares podem ser alterados com o filtro site_status_persistent_object_cache_thresholds, e site_status_should_suggest_persistent_object_cache ignora toda a verificação. Os alojamentos que fornecem object cache noutra camada usam esse filtro, por isso a ausência de aviso não prova que exista um drop-in: verifique se existe wp-content/object-cache.php ou chame wp_using_ext_object_cache().
Erros comuns de caching no WordPress e soluções
Como evitar caching excessivamente agressivo
Problema: colocar tudo em cache leva a conteúdo desatualizado e a utilizadores frustrados.
Solução: implementar exclusões de cache bem pensadas:
function should_cache_page() {
// Don't cache for logged-in users
if (is_user_logged_in()) {
return false;
}
// Don't cache dynamic forms
if (is_page('contact') || is_page('checkout')) {
return false;
}
// Don't cache search results (too many variations)
if (is_search()) {
return false;
}
return true;
}Como prevenir um cache stampede
Problema: quando um item de cache popular expira, centenas de pedidos simultâneos encontram uma falha de cache ao mesmo tempo. Cada processo lança a mesma consulta SQL pesada ou a mesma chamada a uma API remota contra a origem, o que provoca picos de CPU, contenção de bloqueios e quedas do servidor (dogpiling ou thundering herd).
Solução: implementar expiração antecipada probabilística com o algoritmo XFetch ou bloqueios mutex distribuídos com Redis:
Expiração antecipada probabilística com o algoritmo XFetch
Esta abordagem evita por completo bloqueios rígidos. Proposto por Vattani, Chierichetti e Lowenstein, o algoritmo XFetch calcula uma probabilidade de atualização antecipada que cresce à medida que o item se aproxima da expiração e à medida que o tempo de cálculo aumenta. Um worker atualiza o item de cache antes do tempo, enquanto todos os pedidos continuam a receber dados válidos da cache:
/**
* Probabilistic early cache recomputation (XFetch algorithm).
*
* @param string $key Cache identifier
* @param callable $compute Callback generating fresh data
* @param int $ttl Cache TTL in seconds
* @param float $beta Aggressiveness multiplier (> 0, default 1.0)
* @return mixed Cached or computed payload
*/
function xfetch_cache_get($key, callable $compute, $ttl = 3600, $beta = 1.0) {
$cached = wp_cache_get($key, 'xfetch_group');
// Expected format: ['data' => ..., 'delta' => compute_time_seconds, 'expiry' => timestamp]
if (is_array($cached) && isset($cached['data'], $cached['delta'], $cached['expiry'])) {
$remaining = $cached['expiry'] - time();
// Compute probabilistic trigger: -beta * delta * ln(random_float(0, 1))
$random = mt_rand(1, 1000000) / 1000000;
$early_trigger = -($beta * $cached['delta'] * log($random));
// If remaining lifetime exceeds early trigger threshold, return cached value immediately
if ($remaining > $early_trigger) {
return $cached['data'];
}
}
// Refresh cache item
$start = microtime(true);
$fresh_data = $compute();
$delta = microtime(true) - $start;
$payload = [
'data' => $fresh_data,
'delta' => max($delta, 0.001),
'expiry' => time() + $ttl,
];
wp_cache_set($key, $payload, 'xfetch_group', $ttl * 2);
return $fresh_data;
}Bloqueio mutex distribuído com Redis
Quando o cálculo antecipado em segundo plano não é adequado, um bloqueio mutex distribuído garante que exatamente um processo PHP-FPM recalcula a consulta pesada:
function get_cached_with_distributed_lock($key, callable $compute, $ttl = 3600) {
$data = wp_cache_get($key, 'distributed_cache');
if (false !== $data) {
return $data;
}
$lock_key = 'lock:' . $key;
// Atomic SET lock:key token NX EX 10
$lock_acquired = wp_cache_add($lock_key, 1, 'distributed_locks', 10);
if ($lock_acquired) {
try {
$fresh_data = $compute();
wp_cache_set($key, $fresh_data, 'distributed_cache', $ttl);
return $fresh_data;
} finally {
wp_cache_delete($lock_key, 'distributed_locks');
}
}
// If another process holds the lock, wait briefly or serve fallback data
usleep(80000); // 80ms sleep
$data = wp_cache_get($key, 'distributed_cache');
return (false !== $data) ? $data : $compute();
}Como variar a cache para dispositivos móveis
Problema: os utilizadores de desktop e de dispositivos móveis recebem a mesma versão em cache, o que quebra o design responsivo.
Solução: variar a cache por tipo de dispositivo:
// Add device type to cache key
function get_device_cache_key($base_key) {
$device = wp_is_mobile() ? 'mobile' : 'desktop';
return "{$base_key}_{$device}";
}
// Or use Vary header for CDN
function add_device_vary_header($headers) {
$headers['Vary'] = 'User-Agent';
return $headers;
}Conclusão
O caching já não serve para poupar espaço, serve para poupar tempo. Em 2026, uma estratégia de caching inteligente é a diferença entre um site WordPress de nível empresarial e um concorrente que fica para trás.
Ao dominar edge caching, cache tags, fragment caching e padrões SWR, o seu site WordPress torna-se um motor distribuído de alta performance, capaz de lidar com milhões de pedidos com tempos de resposta abaixo de 50 ms.
Pontos principais:
- Implementar edge caching em três camadas para performance global
- Usar cache tagging para invalidação cirúrgica
- Usar Redis 8+ para object caching empresarial
- Combinar conteúdo estático e dinâmico com fragment caching
- Adotar SWR para experiências instantâneas
- Monitorizar continuamente a saúde da cache
- Testar a fundo, porque os bugs em cache são mais difíceis de encontrar
A cache do seu site trabalha a seu favor ou contra si? Contacte a WPPoland para auditar e melhorar hoje a sua arquitetura de cache.
Recursos relacionados
- Segurança e performance WordPress: guia completo do administrador: estratégias de otimização de performance
- O guia definitivo de performance WooCommerce, edição 2026: estratégias de caching para comércio eletrónico
- Guia de otimização da base de dados WordPress: consultas, índices e autoload






