Cache de avanço e retorno

O cache de avanço e retorno (ou bfcache) é uma otimização de navegador que permite a navegação instantânea para frente e para trás. Isso melhora significativamente a experiência de navegação, especialmente para usuários com redes ou dispositivos mais lentos.

Esta página descreve como otimizar suas páginas para o bfcache em todos os navegadores.

Compatibilidade com navegadores

O bfcache é compatível com o Firefox e o Safari há muitos anos, em computadores e dispositivos móveis.

A partir da versão 86, o Chrome ativou o bfcache para navegações entre sites no Android para uma pequena porcentagem de usuários. Nas versões subsequentes, o suporte adicional será lançado lentamente. Desde a versão 96, o bfcache está ativado para todos os usuários do Chrome em computadores e dispositivos móveis.

Noções básicas do bfcache

O bfcache é um cache na memória que armazena um snapshot completo de uma página à medida que o usuário navega. Com a página inteira na memória, o navegador poderá restaurá-la rapidamente se o usuário decidir retornar, em vez de precisar repetir todas as solicitações de rede necessárias para carregar a página.

O vídeo a seguir mostra o quanto o bfcache pode acelerar a navegação:

O uso do bfcache faz com que as páginas sejam carregadas muito mais rapidamente durante a navegação de retorno e avanço.

Os dados de uso do Chrome mostram que 1 em 10 navegações no computador e 1 em cada 5 em dispositivos móveis são de avanço e retorno. Por isso, o bfcache tem o potencial de economizar muito tempo e uso de dados.

Como funciona o "cache"

O "cache" usado pelo bfcache é diferente do cache HTTP, que desempenha seu próprio papel para acelerar navegações repetidas. O bfcache é um snapshot de toda a página na memória, incluindo a heap JavaScript, enquanto o cache HTTP contém apenas as respostas das solicitações feitas anteriormente. Como é muito raro que todas as solicitações necessárias para carregar uma página sejam compatíveis com o cache HTTP, visitas repetidas usando restaurações bfcache são sempre mais rápidas do que até mesmo as navegações sem bfcache mais otimizadas.

No entanto, criar um snapshot de uma página na memória envolve certa complexidade em relação à melhor forma de preservar o código em andamento. Por exemplo, como você lida com chamadas setTimeout() em que o tempo limite é atingido enquanto a página está no bfcache?

A resposta é que os navegadores pausam todos os timers pendentes ou promessas não resolvidas para páginas no bfcache, incluindo quase todas as tarefas pendentes nas filas de tarefas do JavaScript, e retomam as tarefas de processamento se a página for restaurada do bfcache.

Em alguns casos, como tempos limite e promessas, isso é um risco razoavelmente baixo, mas em outros pode levar a um comportamento confuso ou inesperado. Por exemplo, se o navegador pausar uma tarefa necessária para uma transação do IndexedDB, isso poderá afetar outras guias abertas na mesma origem, porque os mesmos bancos de dados do IndexedDB podem ser acessados por várias guias simultaneamente. Como resultado, os navegadores geralmente não tentam armazenar páginas em cache no meio de uma transação do IndexedDB ou ao usar APIs que possam afetar outras páginas.

Para ver mais detalhes sobre como o uso da API afeta a qualificação para o bfcache de uma página, consulte Otimizar suas páginas para o bfcache.

O bfcache e os apps de página única (SPA)

Como o bfcache funciona com navegações gerenciadas pelo navegador, ele não funciona para "navegações suaves" em um app de página única (SPA). No entanto, o bfcache ainda pode ajudar ao sair e retornar a um SPA.

APIs para observar o bfcache

Embora o bfcache seja uma otimização automática feita pelos navegadores, ainda é importante que os desenvolvedores saibam quando isso está acontecendo para que possam otimizar as páginas e ajustar qualquer métrica ou medição de desempenho adequadamente.

Os principais eventos usados para observar o bfcache são os eventos de transição de página pageshow e pagehide, que têm suporte da maioria dos navegadores.

Os eventos mais recentes do ciclo de vida da página, freeze e resume, também são enviados quando as páginas entram ou saem do bfcache e em algumas outras situações, por exemplo, quando uma guia em segundo plano é congelada para minimizar o uso da CPU. Esses eventos só são compatíveis com navegadores baseados no Chromium.

Observar quando uma página é restaurada do bfcache

O evento pageshow é disparado logo após o load quando a página está sendo carregada pela primeira vez e sempre que ela é restaurada do bfcache. O evento pageshow tem uma propriedade persisted, que será true se a página tiver sido restaurada do bfcache e false caso contrário. É possível usar a propriedade persisted para distinguir os carregamentos de página regulares das restaurações do bfcache. Exemplo:

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    console.log('This page was restored from the bfcache.');
  } else {
    console.log('This page was loaded normally.');
  }
});

Em navegadores compatíveis com a API Page Lifecycle, o evento resume é disparado quando as páginas são restauradas do bfcache (imediatamente antes do evento pageshow) e quando um usuário acessa novamente uma guia em segundo plano congelada. Se você quiser atualizar o estado de uma página depois que ela for congelada (o que inclui páginas no bfcache), poderá usar o evento resume. No entanto, se quiser medir a taxa de ocorrências do bfcache do site, será necessário usar o evento pageshow. Em alguns casos, talvez seja necessário usar ambos.

Para detalhes sobre as práticas recomendadas de medição do bfcache, consulte Como o bfcache afeta a análise e a medição de desempenho.

Observar quando uma página está entrando no bfcache

O evento pagehide é disparado quando uma página é descarregada ou quando o navegador tenta colocá-la no bfcache.

O evento pagehide também tem uma propriedade persisted. Se for false, pode ter certeza de que a página não vai entrar no bfcache. No entanto, o fato de persisted ser true não garante que uma página será armazenada em cache. Isso significa que o navegador intends armazenar a página em cache, mas pode haver outros fatores que impossibilitam o armazenamento em cache.

window.addEventListener('pagehide', (event) => {
  if (event.persisted) {
    console.log('This page *might* be entering the bfcache.');
  } else {
    console.log('This page will unload normally and be discarded.');
  }
});

Da mesma forma, o evento freeze é disparado imediatamente após o evento pagehide se persisted for true, mas isso significa que o navegador intends armazenar a página em cache. Talvez seja necessário descartá-lo por vários motivos explicados posteriormente.

Otimizar suas páginas para o bfcache

Nem todas as páginas são armazenadas no bfcache e, mesmo quando uma página é armazenada lá, ela não fica lá indefinidamente. As páginas a seguir descrevem o que torna as páginas qualificadas para o uso do bfcache e recomendam práticas recomendadas para maximizar a capacidade do navegador de armazenar a página em cache e melhorar as taxas de ocorrência em cache.

Usar pagehide em vez de unload

A maneira mais importante de otimizar para o bfcache em todos os navegadores é nunca usar listeners de eventos unload. Detecte pagehide, porque ele é acionado quando uma página entra no bfcache e sempre que unload é acionado.

unload é um recurso mais antigo, criado para ser acionado sempre que um usuário sair de uma página. Esse não é mais o caso, mas muitas páginas da Web ainda operam com o pressuposto de que os navegadores usam unload dessa maneira e que, após o acionamento de unload, a página descarregada deixa de existir. Isso poderá corromper o bfcache se o navegador tentar armazenar uma página descarregada em cache.

No computador, o Chrome e o Firefox tornam as páginas com listeners unload não qualificadas para o bfcache, o que reduz os riscos, mas também faz com que muitas páginas não sejam armazenadas em cache e, por isso, a atualização fique muito mais lenta. O Safari tenta armazenar em cache algumas páginas com listeners de eventos unload. No entanto, para reduzir possíveis falhas, ele não executa o evento unload quando um usuário sai da página, o que torna os listeners de unload não confiáveis.

Em dispositivos móveis, o Chrome e o Safari tentam armazenar páginas em cache com listeners de eventos unload, porque a falta de confiabilidade do unload nesses dispositivos diminui o risco de falha. O Firefox para dispositivos móveis trata as páginas que usam unload como não qualificadas para o bfcache, exceto no iOS, que exige que todos os navegadores usem o mecanismo de renderização WebKit. Por isso, ele se comporta como o Safari.

Para determinar se qualquer JavaScript nas suas páginas usa unload, recomendamos usar a auditoria no-unload-listeners no Lighthouse.

Para informações sobre o plano do Chrome de descontinuar unload, consulte Suspensão do uso do evento de descarregamento.

Usar a política de permissão para evitar que gerenciadores de descarregamento sejam usados em uma página

Alguns scripts e extensões de terceiros podem adicionar gerenciadores de descarregamento a uma página, tornando o site desagradável, tornando-o desqualificado para o bfcache. Para evitar isso no Chrome 115 e em versões mais recentes, use uma política de permissões.

Permission-Policy: unload()

Adicione apenas listeners beforeunload condicionalmente

O evento beforeunload não impede a qualificação das páginas para o cache de avanço e retorno. No entanto, ela não é confiável. Por isso, recomendamos o uso apenas quando for absolutamente necessário.

Um exemplo de caso de uso do beforeunload é avisar ao usuário que ele tem mudanças não salvas que serão perdidas se sair da página. Nesse caso, recomendamos adicionar listeners beforeunload somente quando um usuário tiver mudanças não salvas e removê-los imediatamente após as alterações não salvas serem salvas, como no código a seguir:

function beforeUnloadListener(event) {
  event.preventDefault();
  return event.returnValue = 'Are you sure you want to exit?';
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  window.addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  window.removeEventListener('beforeunload', beforeUnloadListener);
});

Minimizar o uso de Cache-Control: no-store

Cache-Control: no-store é um servidor da Web de cabeçalho HTTP que pode ser definido em respostas que instrui o navegador a não armazenar a resposta em nenhum cache HTTP. É usado em recursos que contêm informações sensíveis do usuário, como páginas por trás de um login.

Embora o bfcache não seja um cache HTTP, os navegadores costumam excluir páginas do bfcache quando Cache-Control: no-store está definido no recurso da página, mas não em qualquer sub-recurso. O Chrome está trabalhando para mudar esse comportamento e manter a privacidade do usuário, mas, por padrão, as páginas que usam Cache-Control: no-store não estão qualificadas para o bfcache.

Para otimizar o bfcache, use Cache-Control: no-store somente em páginas que contenham informações confidenciais que não podem ser armazenadas em cache.

Para páginas que querem sempre veicular conteúdo atualizado, mas não incluem informações confidenciais, use Cache-Control: no-cache ou Cache-Control: max-age=0. Elas instruem o navegador a revalidar o conteúdo antes de exibi-lo e não afetam a qualificação para o bfcache de uma página, já que a restauração de uma página do bfcache não envolve o cache HTTP.

Se o conteúdo mudar a cada minuto, busque atualizações usando o evento pageshow para manter a página atualizada, conforme descrito na próxima seção.

Atualizar dados desatualizados ou confidenciais após a restauração do bfcache

Se o site mantiver dados de estado do usuário, e especialmente se esses dados incluírem informações confidenciais dele, eles precisarão ser atualizados ou apagados depois que uma página for restaurada do bfcache.

Por exemplo, se um usuário sai de um site em um computador público e o próximo usuário clica no botão "Voltar", os dados desatualizados do bfcache podem incluir dados particulares que o primeiro usuário esperava que fossem apagados ao sair.

Para evitar situações como essa, sempre atualize a página após um evento pageshow se event.persisted for true:

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Do any checks and updates to the page
  }
});

Para algumas mudanças, convém forçar uma atualização completa, preservando o histórico de navegação para frente. O código a seguir verifica a presença de um cookie específico do site no evento pageshow e recarrega se o cookie não for encontrado:

window.addEventListener('pageshow', (event) => {
  if (event.persisted && !document.cookie.match(/my-cookie)) {
    // Force a reload if the user has logged out.
    location.reload();
  }
});

Restauração de anúncios e bfcache

Pode ser tentador tentar evitar o uso do bfcache para que sua página possa veicular um novo conjunto de anúncios em cada navegação de retorno ou avanço. No entanto, isso é ruim para o desempenho do site e não aumenta o engajamento com o anúncio de maneira consistente. Por exemplo, um usuário pode ter a intenção de retornar a uma página para clicar em um anúncio, mas, se a página for recarregada em vez de restaurada do bfcache, um anúncio diferente poderá ser mostrado. Recomendamos usar o teste A/B para determinar a melhor estratégia para sua página.

Para sites que querem atualizar os anúncios na restauração do bfcache, é possível atualizar apenas os anúncios no evento pageshow quando event.persisted for true, sem afetar o desempenho da página, como neste exemplo da tag do Google Publishing. Para mais informações sobre as práticas recomendadas para seu site, consulte seu provedor de anúncios.

Evitar referências window.opener

Em navegadores mais antigos, se uma página fosse aberta usando window.open() em um link com target=_blank, sem especificar rel="noopener", a página de abertura teria uma referência ao objeto da janela da página aberta.

Além de ser um risco de segurança, uma página com uma referência não nula window.opener não pode ser colocada com segurança em bfcache, porque isso pode corromper quaisquer páginas que tentarem acessá-la.

Para evitar esses riscos, use rel="noopener" para impedir a criação de referências de window.opener. Esse é o comportamento padrão em todos os navegadores mais recentes. Caso seu site precise abrir uma janela e controlá-la usando window.postMessage() ou referenciando diretamente o objeto "window", nem a janela aberta nem o abridor estão qualificados para o bfcache.

Feche as conexões abertas antes que o usuário saia da página

Como mencionado anteriormente, quando uma página é colocada no bfcache, ela pausa todas as tarefas JavaScript programadas e as retoma quando a página é retirada do cache.

Se essas tarefas programadas do JavaScript acessarem apenas APIs do DOM ou outras APIs isoladas à página atual, não vai causar problemas pausar essas tarefas enquanto a página não estiver visível para o usuário.

No entanto, se essas tarefas estiverem conectadas a APIs que também podem ser acessadas por outras páginas na mesma origem (por exemplo: IndexedDB, Web Locks e WebSockets), pausá-las poderá interromper a execução dessas páginas, impedindo que o código delas seja executado.

Como resultado, alguns navegadores não tentarão colocar uma página em bfcache se ela tiver uma das seguintes opções:

Caso sua página esteja usando uma dessas APIs, é recomendável fechar as conexões e remover ou desconectar os observadores durante o evento pagehide ou freeze. Isso permite que o navegador armazene a página em cache com segurança sem o risco de afetar outras guias abertas. Em seguida, se a página for restaurada do bfcache, será possível reabrir ou reconectar essas APIs durante o evento pageshow ou resume.

O exemplo a seguir mostra como garantir que as páginas que usam o IndexedDB estejam qualificadas para o bfcache fechando uma conexão aberta no listener de eventos pagehide:

let dbPromise;
function openDB() {
  if (!dbPromise) {
    dbPromise = new Promise((resolve, reject) => {
      const req = indexedDB.open('my-db', 1);
      req.onupgradeneeded = () => req.result.createObjectStore('keyval');
      req.onerror = () => reject(req.error);
      req.onsuccess = () => resolve(req.result);
    });
  }
  return dbPromise;
}

// Close the connection to the database when the user leaves.
window.addEventListener('pagehide', () => {
  if (dbPromise) {
    dbPromise.then(db => db.close());
    dbPromise = null;
  }
});

// Open the connection when the page is loaded or restored from bfcache.
window.addEventListener('pageshow', () => openDB());

Testar para garantir que suas páginas podem ser armazenadas em cache

O Chrome DevTools pode ajudar você a testar suas páginas para garantir que elas estejam otimizadas para o bfcache e identificar problemas que possam impedi-las de serem qualificadas.

Para testar uma página:

  1. Acesse a página no Chrome.
  2. No DevTools, acesse Aplicativo > Cache de avanço e retorno.
  3. Clique no botão Run Test. Em seguida, o DevTools tenta sair e voltar para determinar se a página pode ser restaurada do bfcache.
Painel de cache de avanço e retorno no DevTools
Painel Cache de avanço e retorno no DevTools.

Se o teste for bem-sucedido, o painel vai exibir a mensagem "Restaurado do cache de avanço e retorno". Se ela não for bem-sucedida, o painel vai indicar o motivo. Para ver uma lista completa de motivos, consulte Lista de motivos não restaurados do Chromium.

Se o motivo for algo que você possa abordar como desenvolvedor, o painel o marcará como Acionável.

Falha na geração de relatórios do DevTools para restaurar uma página do bfcache
Um teste bfcache falhou com um resultado útil.

Nessa imagem, o uso de um listener de eventos unload torna a página não qualificada para o bfcache. É possível corrigir isso mudando de unload para pagehide:

O que fazer
window.addEventListener('pagehide', ...);
O que não fazer
window.addEventListener('unload', ...);

O Lighthouse 10.0 também adicionou uma auditoria bfcache, que executa um teste semelhante. Para mais informações, consulte os documentos de auditoria bfcache.

Como o bfcache afeta a medição de análise e desempenho

Se você usa uma ferramenta de análise para rastrear as visitas ao site, pode notar uma redução no número total de visualizações de página informadas, já que o Chrome ativa o bfcache para mais usuários.

Na verdade, é provável que você já esteja subregistrando as visualizações de página de outros navegadores que implementam o bfcache, porque a maioria das bibliotecas de análise conhecidas não rastreia restaurações do bfcache como novas visualizações de página.

Para incluir restaurações do bfcache na contagem de visualizações de página, defina listeners para o evento pageshow e verifique a propriedade persisted.

O exemplo a seguir mostra como fazer isso com o Google Analytics. Outras ferramentas de análise provavelmente usam uma lógica semelhante:

// Send a pageview when the page is first loaded.
gtag('event', 'page_view');

window.addEventListener('pageshow', (event) => {
  // Send another pageview if the page is restored from bfcache.
  if (event.persisted) {
    gtag('event', 'page_view');
  }
});

Meça a proporção de ocorrências do bfcache

Para identificar páginas que ainda não usam o bfcache, meça o tipo de navegação para carregamentos de página da seguinte maneira:

// Send a navigation_type when the page is first loaded.
gtag('event', 'page_view', {
   'navigation_type': performance.getEntriesByType('navigation')[0].type;
});

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // Send another pageview if the page is restored from bfcache.
    gtag('event', 'page_view', {
      'navigation_type': 'back_forward_cache';
    });
  }
});

Calcule a proporção de ocorrência do bfcache usando as contagens de navegações back_forward e back_forward_cache.

Os motivos para que uma navegação de retorno ou de avanço não use o bfcache incluem o seguinte comportamento do usuário:

  • Feche e reinicie o navegador.
  • Duplicar uma guia.
  • Fechamento e restauração de uma guia.

Em alguns desses casos, o navegador pode preservar o tipo de navegação original e mostrar um tipo de back_forward, mesmo que elas não sejam navegações de retorno ou de avanço. Mesmo quando os tipos de navegação são informados corretamente, o bfcache é descartado periodicamente para economizar memória.

Por isso, os proprietários de sites não podem esperar uma proporção de hits do bfcache de 100% para todas as navegações de back_forward. No entanto, medir a proporção pode ajudar a identificar páginas que impedem o uso do bfcache.

A equipe do Chrome está trabalhando em uma API NotRestoredReasons para ajudar a expor os motivos pelos quais as páginas não usam o bfcache, para que os desenvolvedores possam melhorar as taxas de hit do bfcache.

Medição da performance

O bfcache também pode afetar negativamente as métricas de desempenho coletadas no campo, especificamente as que medem os tempos de carregamento da página.

Como as navegações bfcache restauram uma página atual, em vez de iniciar um novo carregamento de página, o número total de carregamentos de página coletados diminui quando o bfcache está ativado. No entanto, os carregamentos de página que o bfcache substituem provavelmente estão entre os carregamentos de página mais rápidos do seu conjunto de dados. Isso ocorre porque os carregamentos de página repetidos, incluindo navegações de retorno e avanço, geralmente são mais rápidos do que os primeiros carregamentos de página devido ao armazenamento em cache HTTP. Portanto, ativar o bfcache pode fazer com que sua análise mostre um carregamento de página mais lento, apesar de melhorar o desempenho do site para o usuário.

Há algumas maneiras de lidar com esse problema. Uma delas é anotar todas as métricas de carregamento de página com o respectivo tipo de navegação: navigate, reload, back_forward ou prerender. Isso permite que você continue monitorando seu desempenho nesses tipos de navegação, mesmo que a distribuição geral tenha uma distorção negativa. Recomendamos essa abordagem para métricas de carregamento de página não centradas no usuário, como Tempo até o primeiro byte (TTFB, na sigla em inglês).

Para métricas centradas no usuário, como as Core Web Vitals, é melhor informar um valor que represente com mais precisão a experiência do usuário.

Impacto nas Core Web Vitals

As Core Web Vitals medem a experiência do usuário em uma página em várias dimensões (velocidade de carregamento, interatividade e estabilidade visual). É importante que as métricas das Core Web Vitals reflitam o fato de que os usuários encontram restaurações do bfcache como navegações mais rápidas do que o carregamento de página padrão.

Ferramentas que coletam e informam sobre as Core Web Vitals, como o Chrome User Experience Report, tratam restaurações do bfcache como visitas separadas à página no conjunto de dados. Embora não haja APIs de desempenho da Web dedicadas para medir essas métricas após as restaurações do bfcache, é possível aproximar os valores usando as APIs da Web atuais:

Para mais detalhes sobre como o bfcache afeta cada métrica, consulte as páginas de guias de métricas individuais do Core Web Vitals. Para ver um exemplo específico de como implementar versões do bfcache dessas métricas, consulte PR adicionando-as à biblioteca JS da web-vitals.

Outros recursos