Estratégias avançadas de caching para WordPress 2026: além de ficheiros estáticos simples

Estratégias avançadas de caching para WordPress 2026: além de ficheiros estáticos simples

Última verificação: 13 de outubro de 2026
26 min de leitura
Guia
Core Web Vitals
Desenvolvedor full-stack

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: On

#Camada 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 individual
  • category_{slug}: páginas de arquivo de categoria
  • author_{id}: páginas de arquivo de autor
  • taxonomy_{name}_{term}: páginas de taxonomias personalizadas
  • widget_{id}: conteúdo de widgets dinâmicos
  • global: 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ísticaRedis 8+Memcached
Tipos de dados8+ (strings, hashes, listas, sets, sorted sets, JSON, streams)1 (strings)
PersistênciaRDB + AOFNenhuma
ReplicaçãoMaster-slave + ClusterNenhuma
Eficiência de memóriaMelhor com compressãoBoa
Invalidação por tagsManual (por exemplo, conjuntos de chaves)Implementação manual
Capacidade de consultaRedisSearchNenhuma

#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 para wp_cache_set() no grupo transient e não toca na tabela wp_options.
  • Sem drop-in persistente, os transients são gravados em wp_options como linhas (_transient_* e _transient_timeout_*). No código de set_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 10

Integraçã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çalhos Cache-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 localStorage e sessionStorage do 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

  1. O utilizador pede uma página
  2. O browser verifica a cache
  3. Se existir conteúdo em cache (mesmo expirado), é servido de imediato
  4. Em segundo plano, é obtido conteúdo atualizado da origem
  5. A cache é atualizada e, opcionalmente, o conteúdo da página é refrescado

Cabeçalho Cache-Control:

Cache-Control: max-age=60, stale-while-revalidate=300

Isto 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:

DiretivaDefinição da MDN (resumo)Uso no WordPress
s-maxageValidade numa cache partilhada; aí sobrepõe-se a max-age e a ExpiresTTL longo no edge para HTML anónimo, com max-age curto no browser
stale-while-revalidateA cache pode reutilizar uma resposta desatualizada enquanto a revalidaPáginas de arquivo e artigos que mudam algumas vezes por dia
stale-if-errorA cache pode reutilizar uma resposta desatualizada quando a origem devolve 500, 502, 503 ou 504Mantém as páginas online durante uma falha do PHP-FPM ou da base de dados
privateSó uma cache privada, como a do browser, pode guardar a respostaPáginas de conta, vistas com sessão iniciada
no-storeNenhuma cache pode guardar a respostaCarrinho, 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:

  1. Implementar edge caching em três camadas para performance global
  2. Usar cache tagging para invalidação cirúrgica
  3. Usar Redis 8+ para object caching empresarial
  4. Combinar conteúdo estático e dinâmico com fragment caching
  5. Adotar SWR para experiências instantâneas
  6. Monitorizar continuamente a saúde da cache
  7. 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

Próximo passo

Transforme o artigo numa implementação real

Este bloco reforça a ligação interna e conduz o leitor para o passo seguinte mais útil dentro da arquitetura do site.

Quer implementar isto no seu site?

Se o problema está nos Core Web Vitals, no rendering lento ou no peso do WordPress, posso mapear e implementar a otimização.

Cluster relacionado

Explorar outros serviços WordPress e base de conhecimento

Reforce o seu negócio com suporte técnico profissional em áreas-chave do ecossistema WordPress.

Perguntas frequentes

Respostas práticas para aplicar o tema na execução real.

O Edge Caching torna plugins como WP Rocket obsoletos?#
Não completamente. Enquanto o Edge trata da entrega, os plugins de caching local em 2026 focam-se na otimização de ficheiros, geração de CSS crítico e ajuste específico de base de dados local que serviços edge não conseguem realizar.
Como lidar com utilizadores autenticados com caching?#
Usando Cache Varying via cookies ou service workers, garantindo que utilizadores autenticados recebem uma experiência personalizada enquanto beneficiam da velocidade do object cache para consultas a base de dados.
Qual é o maior risco do caching agressivo?#
Conteúdo desatualizado. Em 2026, mitigamos isto com padrões Stale-While-Revalidate (SWR) e hooks de purga instantânea via API que limpam conteúdo específico sem afetar toda a cache.
Redis é melhor que Memcached para WordPress?#
Para a maioria dos sites WordPress, o Redis é a opção mais flexível: oferece mais tipos de dados, persistência opcional RDB ou AOF e replicação, enquanto o Memcached é um armazenamento chave-valor em memória com remoção LRU e sem replicação. Nenhum dos dois tem cache tags nativas.
Quanto pode o caching avançado melhorar a velocidade do site?#
Depende de onde o tempo se perde hoje. Meça primeiro: o Site Health do WordPress considera bom um tempo de resposta mediano da página inicial abaixo de 600 ms, e o HTML em cache no edge não passa pelo PHP nem pela base de dados, por isso o ganho é maior em sites dominados por HTML sem cache e consultas lentas.

Precisa de FAQ adaptado ao setor e mercado? Criamos uma versão alinhada com os seus objetivos de negócio.

Fale connosco

Artigos Relacionados