Use Cases (Atualizado: 02/06/2026)

Criar uma PWA com Claude Code: manifest, Service Worker e offline

Guia pratico para criar uma PWA com Claude Code: manifest, icones, Service Worker, fallback offline, cache e validacao.

Criar uma PWA com Claude Code: manifest, Service Worker e offline

Uma PWA, ou Progressive Web App, e uma aplicacao web que pode se aproximar da experiencia de um app instalado: nome e icone, abertura em janela standalone, recursos em cache e uma tela offline util quando a conexao falha.

O erro comum e achar que basta criar um manifest.webmanifest. Uma PWA confiavel tambem precisa de icones corretos, Service Worker, pagina de fallback offline, estrategia de cache, checagem de instalabilidade e validacao no Chrome DevTools e no Lighthouse. Um unico icone com 404 ou um HTML antigo preso no cache pode gerar erro so depois do deploy.

Este guia mostra como implementar uma PWA com Claude Code de um jeito pratico e verificavel. Se voce ainda esta comecando, leia antes o guia inicial de Claude Code. Para conferir as decisoes tecnicas, use as fontes oficiais: web.dev Learn PWA, MDN sobre PWA instalaveis, MDN boas praticas de PWA, Chrome sobre criterios de instalacao e a documentacao oficial do Claude Code.

Visao geral

Uma PWA e um conjunto de partes conectadas. O HTML aponta para o manifest, a aplicacao registra o Service Worker, e o Service Worker decide como responder a requisicoes dentro do seu scope: rede, cache ou fallback offline.

Usuario abre o site
  -> index.html referencia manifest.webmanifest
  -> register-sw.js registra /sw.js
  -> sw.js precacheia o app shell
  -> fetch escolhe estrategia por tipo de recurso
  -> navegacao offline recebe offline.html

Antes de editar, defina tres pontos.

DecisaoExemploRisco se ignorar
URL inicial e scope/ ou /app/O Service Worker nao controla as paginas certas
Recursos em cacheHTML, CSS, JS, imagens, offline.htmlVersoes antigas e 404 ficam persistentes
Comportamento offlinePagina offline, ultima pagina, erro APIUsuario nao entende o estado do app

Para blogs, cursos e dashboards pequenos, comece de forma conservadora: Network First para navegacao HTML, Cache First para imagens e icones, Stale While Revalidate para CSS, JavaScript e fontes. Para aprofundar, veja tambem o artigo de estrategias de cache com Claude Code.

Prompt para Claude Code

Claude Code responde melhor quando voce especifica arquivos, regras e verificacoes.

Transforme este app Vite/React existente em uma PWA.

Requisitos:
- Adicionar public/manifest.webmanifest
- Referenciar icones PNG 192x192, 512x512 e maskable 512x512
- Adicionar public/offline.html
- Adicionar public/sw.js como Service Worker
- Registrar o Service Worker em src/register-sw.js
- Usar Network First para navegacoes HTML
- Usar Cache First para imagens
- Usar Stale While Revalidate para CSS, JS e fontes
- Nao cachear POST nem requisicoes cross-origin
- Mostrar aviso quando uma nova versao do Service Worker estiver disponivel
- Finalizar com checklist manual para DevTools e Lighthouse

Restricoes:
- Nao adicionar fetch handler vazio so para parecer instalavel
- Explicar cada arquivo alterado
- Indicar caminhos que dependem do base path de producao

Em um teste de Masa com uma landing page pequena de curso, o primeiro problema nao foi sintaxe, mas a diferenca entre start_url e scope. Quando a prompt obriga Claude Code a expor essas premissas, a revisao fica bem mais objetiva.

Manifest e icones

Crie public/manifest.webmanifest. name e o nome completo, short_name aparece em areas estreitas, start_url e a pagina inicial do app instalado, e scope define o intervalo de URLs controlado.

{
  "id": "/",
  "name": "ClaudeCodeLab PWA Demo",
  "short_name": "CCLab",
  "description": "Demo PWA offline criada com Claude Code",
  "start_url": "/?source=pwa",
  "scope": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#0f766e",
  "orientation": "portrait-primary",
  "prefer_related_applications": false,
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-maskable-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

Depois referencie no head.

<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#0f766e" />
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />

Use PNGs reais. No minimo, prepare 192x192 e 512x512, alem de um icone maskable 512x512 com area segura em volta da marca. Depois das alteracoes, abra cada URL de icone no navegador e confirme status 200.

Service Worker

Adicione public/sw.js. Este exemplo precacheia o app shell, remove caches antigos e so processa GET do mesmo origin.

const VERSION = "2026-06-02";
const STATIC_CACHE = `static-${VERSION}`;
const RUNTIME_CACHE = `runtime-${VERSION}`;

const APP_SHELL = [
  "/",
  "/offline.html",
  "/manifest.webmanifest",
  "/icons/icon-192.png",
  "/icons/icon-512.png",
  "/icons/icon-maskable-512.png"
];

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

self.addEventListener("activate", (event) => {
  const allowedCaches = [STATIC_CACHE, RUNTIME_CACHE];

  event.waitUntil(
    caches
      .keys()
      .then((keys) =>
        Promise.all(
          keys
            .filter((key) => !allowedCaches.includes(key))
            .map((key) => caches.delete(key))
        )
      )
      .then(() => self.clients.claim())
  );
});

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(networkFirstPage(request));
    return;
  }

  if (request.destination === "image") {
    event.respondWith(cacheFirst(request));
    return;
  }

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

async function networkFirstPage(request) {
  const cache = await caches.open(RUNTIME_CACHE);

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

async function cacheFirst(request) {
  const cached = await caches.match(request);
  if (cached) return cached;

  const response = await fetch(request);
  if (response.ok) {
    const cache = await caches.open(RUNTIME_CACHE);
    await cache.put(request, response.clone());
  }
  return response;
}

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

  const networkPromise = fetch(request)
    .then((response) => {
      if (response.ok) cache.put(request, response.clone());
      return response;
    })
    .catch(() => undefined);

  if (cached) return cached;
  return (await networkPromise) || new Response("Network error", { status: 504 });
}

Este codigo evita cachear POST, recursos externos e APIs privadas. Pagamento, login, carrinho, permissoes e estoque exigem regras explicitas de servidor e autenticacao. Cache nao e so velocidade; tambem e armazenamento persistente.

Offline fallback e registro

public/offline.html deve ser simples e independente de rede externa.

<!doctype html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Sem conexao</title>
  </head>
  <body>
    <main>
      <h1>Voce esta offline</h1>
      <p>Recarregue quando a conexao voltar. Paginas abertas recentemente podem continuar disponiveis.</p>
      <p><a href="/">Voltar ao inicio</a></p>
    </main>
  </body>
</html>

Registre em src/register-sw.js.

export async function registerServiceWorker() {
  if (!("serviceWorker" in navigator)) return;

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

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

        worker.addEventListener("statechange", () => {
          if (worker.state === "installed" && navigator.serviceWorker.controller) {
            document.querySelector("[data-refresh-app]")?.removeAttribute("hidden");
          }
        });
      });
    } catch (error) {
      console.error("Service Worker registration failed:", error);
    }
  });
}

Chame uma vez no ponto de entrada.

import { registerServiceWorker } from "./register-sw.js";

registerServiceWorker();

O aviso de atualizacao evita misturar HTML antigo com JavaScript novo. Esse problema aparece muito em apps que ficam abertos por horas.

Instalacao e validacao

Alguns navegadores Chromium disparam beforeinstallprompt quando a app atende aos criterios. Use isso como melhoria progressiva.

let deferredPrompt = null;

window.addEventListener("beforeinstallprompt", (event) => {
  event.preventDefault();
  deferredPrompt = event;
  document.querySelector("[data-install-app]")?.removeAttribute("hidden");
});

document.querySelector("[data-install-app]")?.addEventListener("click", async () => {
  if (!deferredPrompt) return;
  deferredPrompt.prompt();
  const choice = await deferredPrompt.userChoice;
  console.info("Install result:", choice.outcome);
  deferredPrompt = null;
});

Para validar, nao dependa de uma pontuacao PWA antiga. Verifique Manifest, Service Worker, Cache Storage e modo Offline no DevTools. Use Lighthouse para performance, acessibilidade, boas praticas e SEO.

npm run build
npx serve dist -l 4173
npx lighthouse http://localhost:4173 --view --only-categories=performance,accessibility,best-practices,seo
ChecagemOnde verCriterio
ManifestApplication > Manifestnome, start_url e icones sem erro
Service WorkerApplication > Service Workers/sw.js em activated
OfflineNetwork Offline e reloadoffline.html ou pagina recente aparece
Cache StorageApplication > Cache Storagecaches static/runtime esperados
LighthouseRelatoriosem regressao de performance, SEO, acessibilidade

Casos de uso, CTA e armadilhas

PWA vale mais em uso recorrente: biblioteca de cursos para ler no transporte, dashboard interno aberto todos os dias, guia de evento com rede instavel, ou comercio com muitas imagens. O CTA deve vender continuidade e mensuracao, nao apenas “instalar app”.

No ClaudeCodeLab, meca cliques de instalacao, acessos offline, leitura completa, retorno e cliques para produtos. Templates e packs de prompts ficam na biblioteca de produtos. Para times, entregue tambem revisao de cache, validacao do deploy e eventos de analytics.

Armadilhas comuns: scope e start_url divergentes; HTML em Cache First; respostas privadas no cache; icones com 404; e debug sem remover Service Worker antigo. Em caso de duvida, faca Unregister, limpe Cache Storage e repita o primeiro acesso.

Resultado testado

Em uma landing Vite pequena de curso, Masa viu Claude Code gerar rapidamente manifest, pagina offline e registro. O trabalho real foi conferir URLs de icones, recarregar em Offline e limpar caches antigos depois de novas versoes. A pratica mais segura e lancar primeiro um fallback offline minimo e ampliar o cache apenas para recursos que melhoram de fato a experiencia de quem volta.

#Claude Code #PWA #Service Worker #offline #mobile
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.