Experiências de navegação instantânea

Complemento de 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 normalmente envolve várias etapas. Por exemplo, a compra de um produto em um site de comércio eletrônico pode envolver pesquisar um produto, selecionar um item da lista de resultados, adicioná-lo ao carrinho e concluir a operação ao finalizar a compra.

Em termos técnicos, percorrer páginas diferentes significa fazer uma solicitação de navegação. Como regra geral, não convém usar cabeçalhos Cache-Control de longa duração para armazenar em cache a resposta HTML para uma solicitação de navegação. Normalmente, eles são atendidos 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 acessar a rede toda vez que o usuário acessar uma nova página significa que cada navegação pode ser lenta. No mínimo, isso significa que ela não será rápida de forma confiável.

Para acelerar essas solicitações, se você puder antecipar a ação do usuário, poderá solicitar essas páginas e esses recursos com antecedência e mantê-los em cache por um curto período de tempo 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 a ser pré-buscado.

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

Casos de produção

O MercadoLibre é o maior site de comércio eletrônico da América Latina. Para acelerar as navegações, elas injetam tags <link rel="prefetch"> dinamicamente em algumas partes do fluxo. Por exemplo, em páginas de fichas, elas buscam a próxima página de resultados assim que o usuário rola até a parte de baixo da página:

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

Arquivos pré-buscados são solicitados na prioridade "Mais baixa" 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 é "5 minutos". Os recursos são mantidos por cinco minutos. Depois desse tempo, as regras Cache-Control normais para o recurso são aplicadas.

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

Por exemplo, o portal italiano de esportes Virgilio Sport usa service workers para pré-buscar as postagens mais populares na página inicial. Elas também usam a API Network Information para evitar a pré-busca para usuários em uma conexão 2G.

Logotipo da Virgilio Sport.

Como resultado, mais de três semanas de observação de Virgilio Sport testemunhou um tempo de carregamento da navegação para artigos melhorou 78% e o número de impressões de artigos aumentou 45%.

Uma captura de tela da página inicial 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 substituição para o <link rel="prefetch">, delegando essa tarefa completamente ao service worker.

1. Pré-armazenar em cache páginas estáticas e sub-recursos de páginas

Pré-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 alcançar um objetivo semelhante ao da pré-busca: tornar as navegações mais rápidas.

Pré-armazenamento em cache de páginas estáticas

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

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

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

O armazenamento prévio 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 dar um impulso extra nos 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 do produto para os primeiros itens de uma página desse tipo. Se você já armazenou previamente os sub-recursos da página do produto, isso pode tornar a navegação ainda mais rápida.

Para implementar isso, faça o seguinte:

  • 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. Estender a vida útil 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.

Com os service workers, é possível estender o ciclo de vida das páginas de pré-busca e, ao mesmo tempo, oferecer o benefício extra de disponibilizar esses recursos para uso off-line.

No exemplo anterior, é possível complementar a <link rel="prefetch"> usada para fazer a pré-busca de 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 obsoleta enquanto revalida. Nessa estratégia, as páginas podem ser solicitadas tanto do cache quanto da 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 criada 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 fazer a pré-busca dos primeiros produtos em uma página de informações do produto renderizada no 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 temporariamente o 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 o service worker" para delegar totalmente a tarefa de pré-busca ao service worker. Esse tipo de comunicação pode ser alcançado usando worker.postMessage():

Ícone de uma página fazendo comunicação bidirecional com um service worker.

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

A pré-busca com a janela Workbox pode ser implementada da seguinte maneira:

  • Na página: chame o service worker transmitindo 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 a ser pré-buscado:
addEventListener('message', (event) => {
  if (event.data.type === 'PREFETCH_URLS') {
    // Fetch URLs and store them in the cache
  }
});