Experiências de navegação instantânea

Complementando as técnicas tradicionais de pré-busca com service workers.

Demián Renzulli
Demián Renzulli
Gilberto Cocchi
Gilberto Cocchi

A execução de uma tarefa em um site geralmente envolve várias etapas. Por exemplo, comprar um produto em um site de e-commerce pode envolver pesquisar um produto, escolher um item na lista de resultados, adicionar o item ao carrinho e concluir a operação de finalização da compra.

Em termos técnicos, percorrer páginas diferentes significa fazer uma solicitação de navegação. Como regra geral, não use cabeçalhos Cache-Control de longa duração para armazenar em cache a resposta HTML para uma solicitação de navegação. Normalmente, elas são atendidas pela rede, com Cache-Control: no-cache, para garantir que o HTML e a cadeia de solicitações de rede subsequentes sejam (razoavelmente) atualizados. Infelizmente, ter que ir contra a rede cada vez que o usuário acessa uma nova página significa que a navegação pode ser lenta, no mínimo, significa que ela não será rápida de maneira confiável.

Para acelerar essas solicitações, se você puder antecipar a ação do usuário, solicite essas páginas e recursos com antecedência e mantenha-os no cache por um curto período até que o usuário clique nesses links. Essa técnica é chamada de pré-busca e é geralmente implementada adicionando tags <link rel="prefetch"> às páginas, indicando o recurso que precisa ser pré-buscado.

Neste guia, vamos explorar diferentes maneiras de usar os service workers como um complemento das técnicas de pré-busca tradicionais.

Casos de produção

O MercadoLibre é o maior site de e-commerce da América Latina. Para acelerar as navegações, ele injeta tags <link rel="prefetch"> dinamicamente em algumas partes do fluxo. Por exemplo, em páginas de fichas, eles buscam a próxima página de resultados assim que o usuário rola até o fim:

Captura de tela das páginas de listagem um e dois do Mercado Livre e uma tag de pré-busca de link conectando as duas.

Os arquivos pré-buscados são solicitados com a prioridade "Menor" e armazenados no cache HTTP ou no cache de memória (dependendo se o recurso pode ser armazenado em cache ou não) por um período que varia de acordo com o navegador. Por exemplo, a partir do Chrome 85, esse valor será 5 minutos. Os recursos são mantidos por cinco minutos. Depois disso, as regras Cache-Control normais são aplicadas ao recurso.

O uso do armazenamento em cache do service worker pode ajudar você a estender o ciclo de vida dos recursos de pré-busca além da janela de cinco minutos.

Por exemplo, o portal italiano Virgilio Sport (em italiano) usa service workers para fazer a pré-busca das postagens mais acessadas na página inicial. Eles também usam a API Network Information para evitar a pré-busca para usuários com uma conexão 2G.

Logotipo da Virgilio Sport.

Como resultado, em três semanas de observação, Virgilio Sport observou um aumento no tempo de carregamento de 78% para as matérias, e o número de impressões de matérias aumentou 45%.

Uma captura de tela das páginas iniciais e de artigos da Virgilio Sport, com métricas de impacto após a pré-busca.

Implementar o pré-armazenamento em cache com o Workbox

Na seção a seguir, vamos usar o Workbox para mostrar como implementar diferentes técnicas de armazenamento em cache no service worker que podem ser usadas como um complemento ou até mesmo uma substituição para a <link rel="prefetch">, delegando essa tarefa completamente ao service worker.

1. Pré-armazenar em cache as páginas estáticas e os sub-recursos da página

O armazenamento em cache é a capacidade do service worker de salvar arquivos no cache durante a instalação.

Nos casos a seguir, o pré-armazenamento em cache é usado para atingir um objetivo semelhante ao da pré-busca: tornar a navegação mais rápida.

Armazenamento em cache de páginas estáticas

Para páginas geradas em tempo de criação (por exemplo, about.html, contact.html) ou em sites completamente estáticos, é possível apenas adicionar os documentos do site à lista de pré-cache. Assim, eles já estarão disponíveis no cache sempre que o usuário os acessar:

workbox.precaching.precacheAndRoute([
  {url: '/about.html', revision: 'abcd1234'},
  // ... other entries ...
]);

Sub-recursos da página de pré-armazenamento em cache

O pré-armazenamento em cache de recursos estáticos que as diferentes seções do site podem usar (por exemplo, JavaScript, CSS etc.) é uma prática recomendada geral e pode melhorar ainda mais os cenários de pré-busca.

Para acelerar a navegação em um site de e-commerce, use tags <link rel="prefetch"> nas páginas de informações do produto para fazer a pré-busca das páginas de detalhes dos primeiros itens de uma página de informações do produto. Se você já fez o pré-armazenamento em cache dos sub-recursos da página do produto, isso pode tornar a navegação ainda mais rápida.

Para implementar isso:

  • Adicione uma tag <link rel="prefetch"> à página:
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • Adicione os sub-recursos da página à lista de pré-cache no service worker:
workbox.precaching.precacheAndRoute([
  '/styles/product-page.ac29.css',
  // ... other entries ...
]);

2. Ampliar o ciclo de vida dos recursos de pré-busca

Como mencionado anteriormente, o <link rel="prefetch"> busca e mantém recursos no cache HTTP por um período limitado. Depois disso, as regras Cache-Control para um recurso são aplicadas. A partir do Chrome 85, esse valor é de 5 minutos.

Os service workers permitem estender o ciclo de vida das páginas de pré-busca, oferecendo o benefício adicional de disponibilizar esses recursos para uso off-line.

No exemplo anterior, era possível complementar o <link rel="prefetch"> usado para pré-buscar uma página de produto com uma estratégia de armazenamento em cache do ambiente de execução do Workbox.

Para implementar isso:

  • Adicione uma tag <link rel="prefetch"> à página:
 <link rel="prefetch" href="/phones/smartphone-5x.html" as="document">
  • Implemente uma estratégia de armazenamento em cache no ambiente de execução no service worker para estes tipos de solicitações:
new workbox.strategies.StaleWhileRevalidate({
  cacheName: 'document-cache',
  plugins: [
    new workbox.expiration.Plugin({
      maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
    }),
  ],
});

Nesse caso, optamos por usar uma estratégia durante a validação. Nessa estratégia, as páginas podem ser solicitadas no cache e na rede, em paralelo. A resposta vem do cache, se disponível. Caso contrário, vem da rede. O cache é sempre atualizado com a resposta da rede a cada solicitação bem-sucedida.

3. Delegar a pré-busca ao service worker

Na maioria dos casos, a melhor abordagem é usar <link rel="prefetch">. A tag é uma dica de recurso projetada para tornar a pré-busca o mais eficiente possível.

Em alguns casos, no entanto, pode ser melhor delegar essa tarefa completamente ao service worker. Por exemplo: para pré-buscar os primeiros produtos em uma página de informações do produto renderizada do lado do cliente, pode ser necessário injetar várias tags <link rel="prefetch"> dinamicamente na página, com base em uma resposta da API. Isso pode consumir tempo na linha de execução principal da página e dificultar a implementação.

Nesses casos, use uma "estratégia de comunicação de página para service worker", para delegar completamente a tarefa de pré-busca ao service worker. Esse tipo de comunicação pode ser alcançado usando worker.postMessage():

Um ícone de uma página que faz comunicação bidirecional com um service worker.

O pacote de janela de caixa de trabalho simplifica esse tipo de comunicação, abstraindo muitos detalhes da chamada que está sendo feita.

A pré-busca com a janela de caixa de trabalho pode ser implementada da seguinte maneira:

  • Na página, chame o service worker passando o tipo de mensagem e a lista de URLs para pré-busca:
const wb = new Workbox('/sw.js');
wb.register();

const prefetchResponse = await wb.messageSW({type: 'PREFETCH_URLS', urls: […]});
  • No service worker: implemente um gerenciador de mensagens para emitir uma solicitação fetch() para cada URL para pré-busca:
addEventListener('message', (event) => {
  if (event.data.type === 'PREFETCH_URLS') {
    // Fetch URLs and store them in the cache
  }
});