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 é 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
| Caso | Por que ajuda | Cuidado |
|---|---|---|
| Documentação ou blog | Artigos, CSS, imagens e fontes carregam melhor em visitas futuras | HTML antigo pode esconder correções |
| Dashboard SaaS | Estrutura de navegação aparece mesmo com rede fraca | Não cacheie cobrança, conta ou dados privados |
| Formulário de campo | Usuário mantém rascunhos e tarefas offline | POST vai para IndexedDB, não para Cache API |
| Catálogo ecommerce ou mídia | Thumbnails e assets evitam downloads repetidos | Preç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.
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.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Escada de segurança de permissões no Claude Code
Amplie de read-only para edições limitadas, comandos de prova e deploy checks sem perder controle.
Claude Code Small PR Proof Pack: pequenas mudanças fáceis de revisar
Um pacote de prova para PRs do Claude Code: diff, checks, URL pública, CTA e rollback.
Gate de revisão antes do commit com Claude Code
Revisão antes do commit com Claude Code: diff, build, URL pública, Gumroad, consultoria, testes e arquivos fora do escopo.