Tips & Tricks (Atualizado: 02/06/2026)

Service Worker com Claude Code: cache, atualizações e offline

Guia prático para Service Worker com Claude Code: cache, ciclo de atualização, UX offline e exemplos executáveis.

Service Worker com Claude Code: cache, atualizações e offline

Service Worker é uma das bases de PWA e experiência offline, mas também é fácil de usar mal. Se você pedir ao Claude Code apenas “adicione cache”, o app pode manter HTML antigo, salvar dados privados no navegador ou continuar servindo uma versão velha depois do deploy.

Pense no Service Worker como um pequeno proxy entre navegador e servidor. Ele intercepta requisições elegíveis e decide se responde pela rede, pela Cache API ou por uma página offline preparada. Este guia cobre decisões antes da implementação, exemplo copiável, invalidação de cache, ciclo de atualização, UX offline e falhas comuns.

Use referências oficiais durante a implementação: MDN Service Worker API, guia de Service Worker do web.dev, orientação de cache do web.dev e Chrome Workbox docs. Para continuar no ClaudeCodeLab, veja o guia de PWA com Claude Code, estratégias de cache e IndexedDB com Claude Code.

O que um Service Worker faz

Um Service Worker é JavaScript fora da página. Ele não acessa DOM diretamente, então não altera botões, formulários ou estado React. O que ele faz é interceptar requisições e escolher entre rede, cache ou fallback offline.

O script da página acaba quando a aba fecha. O Service Worker é orientado a eventos: o navegador o acorda para install, activate, fetch, push e, em alguns casos, sync em segundo plano. Para a maioria dos apps, comece por fetch, Cache API e ciclo de atualização.

sequenceDiagram
  participant User as Usuário
  participant Page as Página
  participant SW as Service Worker
  participant Cache as Cache API
  participant Net as Servidor
  User->>Page: Abre o site
  Page->>SW: Registra /sw.js
  Page->>SW: Faz uma requisição fetch
  SW->>Cache: Verifica cache
  alt Em cache
    Cache-->>SW: Resposta salva
  else Sem cache
    SW->>Net: Busca resposta nova
    Net-->>SW: Resposta fresca
  end
  SW-->>Page: Resposta para renderizar

Ele é um controlador de tráfego, não uma mágica de performance. A qualidade depende de decidir o que guardar, quando apagar e o que mostrar quando a rede falha.

Casos de uso

CasoPor que ajudaCuidado
Documentação ou blogArtigos, CSS, imagens e fontes carregam melhor em visitas futurasHTML antigo pode esconder correções
Dashboard SaaSEstrutura de navegação aparece mesmo com rede fracaNão cacheie cobrança, conta ou dados privados
Formulário de campoUsuário mantém rascunhos e tarefas offlinePOST vai para IndexedDB, não para Cache API
Catálogo ecommerce ou mídiaThumbnails e assets evitam downloads repetidosPreço, estoque e imagens protegidas precisam de frescor

Masa testou o padrão em um site pequeno de estudos. Cachear imagens e fontes melhorou bastante a segunda visita. Cachear HTML de artigos com Cache First causou um problema: correções demoravam a aparecer. Para Claude Code, a instrução correta é “cacheie estes recursos com estas regras”, não “cacheie tudo”.

Prompt para Claude Code

Inclua proibições e verificação.

Adicione um Service Worker ao app Vite existente.

Requisitos:
- Colocar /sw.js em public e usar scope /
- Cachear apenas assets estáticos com método GET
- Usar Network First para navegação HTML
- Retornar /offline.html quando a navegação falhar offline
- Não cachear API, POST, páginas autenticadas nem outras origens
- Incluir data ou versão no nome do cache
- Apagar caches antigos durante activate
- Mostrar aviso de recarregamento quando o novo worker estiver waiting

Verificação:
- Conferir Chrome DevTools > Application > Service Workers
- Ativar Network Offline e confirmar /offline.html
- Alterar CACHE_VERSION e confirmar exclusão de caches antigos

Essas exclusões são essenciais. Um erro em cache de API ou página privada vira bug de dados, não só de interface.

Implementação mínima copiável

Coloque estes quatro arquivos em uma pasta vazia como sw-demo e rode um servidor local. Service Worker exige HTTPS ou localhost; abrir o HTML direto não funciona.

python -m http.server 5173

Abra http://localhost:5173.

<!-- index.html -->
<!doctype html>
<html lang="pt">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Service Worker Demo</title>
    <style>
      body {
        font-family: system-ui, sans-serif;
        margin: 2rem;
        line-height: 1.7;
      }
      button {
        padding: 0.7rem 1rem;
      }
    </style>
  </head>
  <body>
    <h1>Service Worker Demo</h1>
    <p id="status">Aguardando registro.</p>
    <button type="button" onclick="location.reload()">Recarregar</button>
    <script src="/register-sw.js"></script>
  </body>
</html>
// register-sw.js
const status = document.querySelector("#status");
let reloadRequested = false;
let updatePromptShown = false;

function setStatus(message) {
  if (status) status.textContent = message;
}

function askToReload(worker) {
  if (updatePromptShown) return;
  updatePromptShown = true;

  const ok = window.confirm("Há uma nova versão. Recarregar agora?");

  if (ok) {
    reloadRequested = true;
    worker.postMessage({ type: "SKIP_WAITING" });
  }
}

async function registerServiceWorker() {
  if (!("serviceWorker" in navigator)) {
    setStatus("Este navegador não suporta Service Worker.");
    return;
  }

  try {
    const registration = await navigator.serviceWorker.register("/sw.js", {
      scope: "/",
    });

    setStatus(`Service Worker registrado: ${registration.scope}`);

    if (registration.waiting && navigator.serviceWorker.controller) {
      askToReload(registration.waiting);
    }

    registration.addEventListener("updatefound", () => {
      const worker = registration.installing;
      if (!worker) return;

      worker.addEventListener("statechange", () => {
        const hasOldController = Boolean(navigator.serviceWorker.controller);
        if (worker.state === "installed" && hasOldController) {
          askToReload(worker);
        }
      });
    });
  } catch (error) {
    console.error(error);
    setStatus("Falha ao registrar o Service Worker.");
  }
}

navigator.serviceWorker?.addEventListener("controllerchange", () => {
  if (!reloadRequested) return;
  window.location.reload();
});

registerServiceWorker();
// sw.js
const CACHE_VERSION = "2026-06-02-v1";
const CACHE_PREFIX = "claude-sw-demo";
const CACHE_NAME = `${CACHE_PREFIX}-${CACHE_VERSION}`;

const APP_SHELL = [
  "/",
  "/index.html",
  "/offline.html",
  "/register-sw.js",
];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL)),
  );
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((names) =>
      Promise.all(
        names
          .filter((name) => name.startsWith(CACHE_PREFIX))
          .filter((name) => name !== CACHE_NAME)
          .map((name) => caches.delete(name)),
      ),
    ),
  );
  self.clients.claim();
});

self.addEventListener("message", (event) => {
  if (event.data?.type === "SKIP_WAITING") {
    self.skipWaiting();
  }
});

self.addEventListener("fetch", (event) => {
  const { request } = event;
  if (request.method !== "GET") return;

  const url = new URL(request.url);
  if (url.origin !== self.location.origin) return;

  if (request.mode === "navigate") {
    event.respondWith(networkFirstNavigation(request));
    return;
  }

  if (["style", "script", "font", "image"].includes(request.destination)) {
    event.respondWith(staleWhileRevalidate(request));
  }
});

async function networkFirstNavigation(request) {
  const cache = await caches.open(CACHE_NAME);

  try {
    const response = await fetch(request);
    if (response.ok) cache.put(request, response.clone());
    return response;
  } catch {
    return (
      (await cache.match(request)) ||
      (await cache.match("/offline.html")) ||
      new Response("Offline", { status: 503 })
    );
  }
}

async function staleWhileRevalidate(request) {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(request);

  const fetched = fetch(request)
    .then((response) => {
      if (response.ok) cache.put(request, response.clone());
      return response;
    })
    .catch(() => cached || new Response("Offline", { status: 503 }));

  return cached || fetched;
}
<!-- offline.html -->
<!doctype html>
<html lang="pt">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Você está offline</title>
  </head>
  <body>
    <main>
      <h1>Você está offline</h1>
      <p>Quando a conexão voltar, recarregue esta página.</p>
      <button type="button" onclick="location.reload()">Tentar novamente</button>
    </main>
  </body>
</html>

O exemplo usa Network First para navegação e Stale While Revalidate para CSS, JavaScript, fontes e imagens. Network First consulta o servidor primeiro e só usa cache ou offline.html se falhar. Stale While Revalidate mostra o cache imediatamente e atualiza em segundo plano. Não use sem critério em notícias, preços, estoque ou telas autenticadas.

Atualização e invalidação

O ciclo de atualização é onde muita implementação falha. Quando sw.js muda, o navegador instala um worker novo. Se uma página antiga ainda estiver aberta, o novo worker pode ficar em waiting. O código de registro detecta isso, pergunta ao usuário e envia SKIP_WAITING apenas com confirmação.

O worker então chama self.skipWaiting(), ativa, apaga caches antigos e assume os clientes. Sem esse fluxo, usuários podem continuar com um app-cache-v1 antigo depois de um deploy.

Use nome de cache com data, número de release ou commit. Se o build gera arquivos com hash, a lista de precache precisa acompanhar a saída do build. Quando a lista manual ficar frágil, considere Workbox. Mesmo assim, Workbox não decide quais dados de negócio podem ser cacheados.

UX offline

Suporte offline não termina ao responder pela Cache API. O usuário precisa saber se o trabalho foi salvo, está aguardando sync ou falhou. Para formulários, não salve POST na Cache API; salve rascunhos ou jobs em IndexedDB na página e reenvie ao voltar online. Background Sync ajuda, mas o suporte varia, então fluxos críticos também precisam de evento online e botão visível de retry.

Ao pedir para Claude Code, inclua texto offline, botão de tentar novamente, estados de rascunho e mensagens de falha. Em app de campo, diferenciar “enviado”, “salvo neste dispositivo” e “falha de sincronização” reduz confusão.

Falhas comuns

A primeira é scope errado. Um worker em /app/sw.js controla /app/ por padrão, não o site inteiro. Para controle global, coloque em /sw.js e registre com scope /.

A segunda é 404 em cache.addAll(). Uma única URL ausente derruba todo o install. Depois que Claude Code adicionar arquivos, confira DevTools Application e Cache Storage.

A terceira é dado privado. Não cacheie /api/me, páginas de cobrança, HTML admin ou JSON específico do usuário sem estratégia clara de exclusão. Cache do navegador ainda é armazenamento no dispositivo.

A quarta é ignorar UX de atualização. Workers antigos podem manter JS e CSS antigos. Versione caches, apague em activate e permita recarregar quando houver worker waiting.

Por fim, Service Worker não é armazenamento permanente. O navegador pode remover caches se faltar espaço. Respostas opaque cross-origin são difíceis de medir. Ele não acessa DOM. Funciona apenas em HTTPS ou localhost. Não prometa “offline completo” sem testar o fluxo real.

Resumo e CTA

Service Worker melhora revisitas, offline e qualidade PWA. O caminho seguro é definir dono do cache, ciclo de atualização, regras de dados privados e telas offline antes de Claude Code editar arquivos.

ClaudeCodeLab ajuda com conversão PWA, desenho de cache, formulários offline, migração para Workbox e revisão de implementação com Claude Code. Se seu site precisa ficar mais rápido sem servir dados antigos ou privados, comece por treinamento e consultoria Claude Code.

Testando essa configuração mínima no Chrome local, depois do primeiro carregamento aparece claude-sw-demo-2026-06-02-v1 no painel Application. Ao colocar Network em Offline e recarregar, offline.html aparece. Ao mudar CACHE_VERSION, os caches antigos são removidos no activate, tornando o exemplo uma boa base de verificação.

#Claude Code #Service Worker #PWA #offline #caching
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.