W3docs

Service Workers

Aprenda Service Workers em JavaScript: ciclo de vida, registro, estratégias de cache, suporte offline e como enviar atualizações com versionamento de cache.

Service Workers: Criando Aplicações Web Poderosas com Suporte Offline

Um Service Worker é um script que o navegador executa em segundo plano, separado da sua página web, sem acesso direto ao DOM. Ele fica entre sua aplicação web e a rede como um proxy programável: toda requisição feita pela página pode ser interceptada, inspecionada, servida a partir de um cache ou reescrita antes de chegar ao servidor.

Essa única capacidade desbloqueia os recursos que os usuários esperam de aplicações web modernas: funcionamento offline, carregamentos repetidos quase instantâneos, sincronização de dados em segundo plano e notificações push. Service Workers são o motor por trás das Progressive Web Apps (PWAs).

Este capítulo explica o que são Service Workers, o ciclo de vida pelo qual passam, como registrar um, as estratégias de cache mais comuns e como enviar atualizações sem servir arquivos desatualizados. A API de Service Worker é construída inteiramente sobre Promises, portanto um bom entendimento de async/await e da Fetch API será útil.

O que é um Service Worker?

Um Service Worker é um tipo de web worker: um arquivo JavaScript que roda em sua própria thread, independente da página que o registrou. Por rodar fora da thread principal, ele não pode bloquear sua UI, mas também não pode acessar o DOM — ele se comunica com as páginas por meio de eventos e mensagens.

Características principais que o diferenciam de um script de página comum:

  • É orientado a eventos. O navegador o inicia quando há trabalho a fazer (um fetch recebido, um push, um sync) e pode encerrá-lo quando estiver ocioso. Nunca presuma que o estado global sobrevive entre eventos.
  • Possui um ciclo de vida. Um Service Worker é instalado, ativado e somente então controla páginas. As atualizações seguem regras rígidas para que os usuários nunca recebam um app parcialmente atualizado.
  • É delimitado por escopo. Um worker só pode interceptar requisições dentro de seu escopo — por padrão, o diretório onde o script reside.
  • Requer um contexto seguro. Service Workers só funcionam via HTTPS (ou localhost durante o desenvolvimento), porque um script capaz de reescrever todas as respostas é uma superfície de ataque séria.

Por que usar Service Workers?

BenefícioO que oferece
Suporte offlineArmazena em cache o shell da aplicação e os recursos essenciais para que o app carregue sem conexão de rede.
DesempenhoVisitas repetidas são servidas a partir de um cache local, eliminando viagens de ida e volta e reduzindo os tempos de carregamento.
Sincronização em segundo planoAdia requisições com falha (por exemplo, um comentário enviado) e as reenvia automaticamente quando a conectividade é restaurada.
Notificações pushRecebe e exibe mensagens de um servidor mesmo quando nenhuma aba está aberta.
Controle total das requisiçõesDecide, por requisição, se deve usar o cache, a rede ou lógica personalizada.

O ciclo de vida do Service Worker

Um Service Worker passa por um conjunto bem definido de estados. Entendê-los é a coisa mais importante para evitar bugs do tipo "por que meu código antigo ainda está sendo executado?".

  1. Registrar — a página chama navigator.serviceWorker.register(). O navegador baixa o script.
  2. Instalar — o evento install é disparado uma vez por versão do worker. É aqui que você faz o pré-cache dos arquivos que o app precisa para funcionar offline.
  3. Aguardar — se um worker mais antigo ainda controla páginas abertas, o novo worker aguarda. Ele não será ativado até que todas as páginas controladas sejam fechadas, a menos que você chame self.skipWaiting().
  4. Ativar — o evento activate é disparado. É aqui que você limpa os caches de versões anteriores.
  5. Controlar / Fetch — uma vez ativo, o worker intercepta eventos fetch de páginas dentro de seu escopo.
register → install → (waiting) → activate → fetch / push / sync ...

Dois métodos orientam esse fluxo:

  • self.skipWaiting() (em install) instrui o novo worker a ativar imediatamente em vez de aguardar.
  • self.clients.claim() (em activate) permite que o worker ativo assuma o controle de páginas já abertas, em vez de controlar apenas páginas carregadas após a ativação.

Por que a fase de espera existe: ela garante que uma única versão do seu código controle uma página durante toda a sua existência, para que você nunca misture HTML antigo com scripts recém-cacheados. Use skipWaiting() de forma deliberada, pois ele pode substituir o worker de controle enquanto um usuário está ativo.

Restrições a ter em mente

  • Somente HTTPS ou localhost. Páginas mistas/inseguras não podem registrar um worker.
  • O escopo limita a interceptação. Um worker em /app/sw.js controla /app/ e subdiretórios — não toda a origem. Coloque o script na raiz do site para controlar tudo.
  • Sem acesso ao DOM. Atualize a página postando mensagens ou fazendo a página ler a partir dos caches.
  • O worker pode ser encerrado a qualquer momento. Armazene tudo que precise persistir no Cache Storage, IndexedDB ou Web Storage — não em variáveis globais do worker.

Passo 1 — Registrar o Service Worker

No JavaScript da sua página, registre o script com navigator.serviceWorker.register(). Sempre faça a detecção de recursos primeiro e registre após o carregamento da página para que o worker não concorra com a primeira renderização:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/sw.js')
      .then((registration) => {
        console.log('Service Worker registered, scope:', registration.scope);
      })
      .catch((error) => {
        console.error('Service Worker registration failed:', error);
      });
  });
}

A chamada register() retorna uma Promise que resolve com um ServiceWorkerRegistration. Seu scope informa quais URLs este worker controla.

Passo 2 — Escrever o script do Service Worker

Crie um arquivo separado (aqui, sw.js) para o próprio worker. Dentro dele, você trata os eventos do ciclo de vida e decide como as requisições são servidas. O exemplo abaixo faz o pré-cache de um shell da aplicação na instalação, limpa caches antigos na ativação e serve uma estratégia de cache-first com fallback offline:

const CACHE_VERSION = 'v1';
const PRECACHE_URLS = ['/', '/index.html', '/styles.css', '/offline.html'];

// Install: pre-cache the app shell.
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_VERSION).then((cache) => cache.addAll(PRECACHE_URLS))
  );
  self.skipWaiting(); // activate this version immediately
});

// Activate: remove caches from previous versions.
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches
      .keys()
      .then((keys) =>
        Promise.all(
          keys
            .filter((key) => key !== CACHE_VERSION)
            .map((key) => caches.delete(key))
        )
      )
      .then(() => self.clients.claim()) // take control of open pages
  );
});

// Fetch: serve from cache, fall back to network, then to the offline page.
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return (
        cached ||
        fetch(event.request).catch(() => caches.match('/offline.html'))
      );
    })
  );
});

Alguns pontos que merecem destaque:

  • event.waitUntil(promise) mantém o worker ativo até que a Promise seja resolvida, para que o navegador não o encerre no meio da instalação ou ativação.
  • event.respondWith(promise) é como você responde a um evento fetch — retorne uma Response (do cache) ou uma Promise que resolva para uma.
  • self.skipWaiting() força a nova versão a ativar sem esperar que as páginas antigas sejam fechadas. Combinado com clients.claim(), o novo worker assume o controle imediatamente. Conveniente durante o desenvolvimento; use com cuidado em produção, pois substituir o worker de controle durante uma sessão pode interromper usuários ativos.

Passo 3 — O worker assume o controle

Após a instalação e a ativação serem concluídas, o worker controla as páginas dentro de seu escopo e seu manipulador fetch intercepta as requisições delas. Note que o primeiro carregamento de uma página não passa pelo worker — o worker está sendo instalado durante esse carregamento. A partir do segundo carregamento, as requisições fluem pelo seu manipulador fetch.

Estratégias comuns de cache

Não existe uma única estratégia de cache "correta" — você escolhe uma por tipo de recurso com base em quão atualizado o dado precisa estar.

EstratégiaComo funcionaMelhor para
Cache firstRetorna a cópia em cache; acessa a rede somente em caso de ausência.Recursos estáticos que raramente mudam (CSS, fontes, o shell da aplicação).
Network firstTenta a rede; volta ao cache em caso de falha.Conteúdo atualizado com frequência (respostas de API, feeds de notícias).
Stale-while-revalidateServe a cópia em cache imediatamente, depois busca uma cópia atualizada em segundo plano para a próxima vez.Recursos onde a velocidade importa mais do que a atualização perfeita (avatares, miniaturas).

Um manipulador network-first tem esta aparência:

self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then((response) => {
        // Cache a copy for offline use, then return the fresh response.
        const copy = response.clone();
        caches.open('v1').then((cache) => cache.put(event.request, copy));
        return response;
      })
      .catch(() => caches.match(event.request))
  );
});

Dica: Um corpo de Response só pode ser lido uma vez, por isso você deve usar clone() antes de armazená-lo em cache e retorná-lo.

Atualizando um Service Worker

Quando você altera sw.js, o navegador detecta a diferença de bytes, baixa o novo arquivo e executa seu evento install. O novo worker então aguarda (a menos que você chame skipWaiting()). O padrão de versionamento de cache acima é o que mantém as atualizações limpas:

  1. Incremente CACHE_VERSION (por exemplo, 'v1''v2') sempre que os recursos em cache mudarem.
  2. O novo install grava os recursos no novo cache.
  3. O novo activate exclui todo cache cuja chave não seja a versão atual, descartando arquivos desatualizados.

Isso garante que os usuários nunca recebam uma mistura de recursos antigos e novos após o deploy.

Exemplo do Mundo Real: Notificações de Status de Conectividade

Este exemplo demonstra um recurso amplamente utilizado em muitos sites e aplicações modernas, como serviços de streaming como Netflix ou aplicações baseadas em nuvem como Google Docs, para informar os usuários sobre seu status de conectividade. Ao notificar os usuários quando estão offline, essas plataformas melhoram a experiência do usuário garantindo que eles estejam cientes de possíveis problemas com sincronização de dados ou interrupções de streaming. Este exemplo foca na integração da UI na thread principal, enquanto o script do Service Worker permanece igual ao do exemplo anterior.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Connectivity Notifier</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        text-align: center;
        margin-top: 50px;
      }
      #status {
        padding: 10px;
        border-radius: 5px;
        color: #fff;
        font-size: 24px;
      }
      .online {
        background-color: #4caf50;
        animation: blinker 1s linear infinite;
      }
      .offline {
        background-color: #f44336;
        animation: blinker 1s linear infinite;
      }
      @keyframes blinker {
        50% {
          opacity: 0.5;
        }
      }
    </style>
  </head>
  <body>
    <h1>Connectivity Notifier</h1>
    <p id="status" class="offline">Checking connectivity...</p>

    <script>
      if ("serviceWorker" in navigator) {
        navigator.serviceWorker.register("sw.js").then(function () {
          console.log("Service Worker Registered");
        });

        window.addEventListener('online', () => {
          const statusElement = document.getElementById("status");
          statusElement.textContent = "Online";
          statusElement.className = "online";
        });

        window.addEventListener('offline', () => {
          const statusElement = document.getElementById("status");
          statusElement.textContent = "Offline";
          statusElement.className = "offline";
        });
      }
    </script>
  </body>
</html>

Explicação:

  • Verificação de conectividade: A página principal escuta os eventos online e offline no objeto window e atualiza a UI imediatamente, evitando a abordagem não confiável de polling.
  • Feedback ao usuário: A página exibe o status atual de conectividade, ajudando os usuários a entender como integrar capacidades em segundo plano com uma interface responsiva.
  • Limpeza do código: O listener morto navigator.serviceWorker.onmessage foi removido, pois o script do Service Worker não envia nenhuma mensagem.

Conclusão

Service Workers transformam o navegador em um proxy de rede programável, tornando possível construir aplicações que são rápidas, resilientes e utilizáveis offline. Os pontos-chave são entender o ciclo de vida (install → wait → activate → fetch), escolher uma estratégia de cache adequada para cada recurso e usar o versionamento de cache para que as atualizações sejam implantadas de forma limpa.

Para aprofundar-se nos blocos de construção dos quais os Service Workers dependem, consulte:

Prática

Prática
Quais são as principais características e funcionalidades dos Service Workers em JavaScript?
Quais são as principais características e funcionalidades dos Service Workers em JavaScript?
Was this page helpful?