O que é um PWA?
Um Progressive Web App é um site que se comporta como um aplicativo instalado: ele funciona offline, carrega instantaneamente em visitas recorrentes e pode ser adicionado à tela inicial ou ao desktop. Não existe uma tecnologia única chamada PWA — trata-se de um conjunto de capacidades que você adota incrementalmente.
Três ingredientes fazem isso funcionar:
- HTTPS, para que o navegador confie na sua origem o suficiente para permitir um service worker.
- Um web manifest que descreve o nome, ícones e modo de exibição do app.
- Um service worker, um script de segundo plano que intercepta requisições de rede e gerencia caches.
O resultado é uma experiência que parece nativa, mas continua sendo um site que você pode atualizar instantaneamente.
O web manifest
O manifest é um arquivo JSON vinculado ao head do HTML. Ele informa à plataforma como apresentar o app quando instalado.
{
"name": "hackweb Reader",
"short_name": "Reader",
"start_url": "/",
"display": "standalone",
"background_color": "#0b0d0c",
"theme_color": "#0b0d0c",
"icons": [
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" }
]
}
<!-- index.html -->
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="theme-color" content="#0b0d0c" />
display: "standalone" oculta a interface do navegador (browser chrome) quando instalado, e os ícones e cores controlam a splash screen e o alternador de tarefas. Um manifest válido somado a um service worker é o que faz com que o navegador ofereça a instalação do app.
O ciclo de vida do service worker
Um service worker é um script separado e sem acesso ao DOM. Ele possui um ciclo de vida distinto: install, activate e fetch.
// register.js
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/sw.js");
});
}
Durante o install, você faz o pré-cache do app shell. Durante o activate, você limpa caches antigos. Durante o fetch, você intercepta requisições e decide como responder. Um novo worker aguarda até que todas as páginas que utilizam a versão anterior sejam fechadas antes de ser ativado, e é por isso que as atualizações às vezes exigem um segundo reload.
Estratégias de cache
O coração de um service worker é a sua estratégia de cache. Diferentes recursos exigem tratamentos diferentes.
- Cache-first — serve do cache e, se não encontrar, recorre à rede. Ideal para assets versionados e imutáveis, como bundles com hash.
- Network-first — tenta a rede e, em caso de falha, recorre ao cache. Bom para HTML e dados que precisam estar atualizados.
- Stale-while-revalidate — serve a versão em cache imediatamente e a atualiza em segundo plano. Ótimo para dados onde um pequeno atraso na atualização é aceitável.
- Cache-only e network-only — para casos específicos de borda.
// strategies.js
self.addEventListener("fetch", (event) => {
const { request } = event;
if (request.destination === "image") {
event.respondWith(
caches.match(request).then((cached) => cached || fetch(request)),
);
}
});
A propriedade request.destination informa qual tipo de recurso está sendo buscado, o que permite aplicar uma estratégia por tipo de asset.
Fallback offline
Mesmo com o cache, algumas requisições irão falhar. Forneça um fallback elegante.
// fallback.js
self.addEventListener("fetch", (event) => {
if (event.request.mode === "navigate") {
event.respondWith(
fetch(event.request).catch(() => caches.match("/offline.html")),
);
}
});
Uma página offline, um shell em cache ou um estado claro de “você está offline” é muito melhor do que o erro padrão do navegador. Combinado com o background sync, as requisições que falharam offline podem ser tentadas automaticamente assim que a conectividade for restaurada.
Atualizações e versionamento
O cache é poderoso, mas fácil de configurar incorretamente. A regra é simples: versione seus caches e delete os antigos ao ativar. Caso contrário, os usuários podem ficar presos a arquivos obsoletos indefinidamente.
// activate.js
const CACHE = "app-v2";
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))),
),
);
});
Para qualquer coisa além do básico, a biblioteca Workbox gerencia precaching, roteamento, expiração e limpeza com padrões bem testados, além de se integrar à maioria das ferramentas de build.
Além do modo offline
Service workers permitem mais do que apenas cache:
- Background sync tenta reenviar requisições que falharam quando a rede retorna.
- Push notifications entregam mensagens mesmo quando o app está fechado.
- Periodic sync atualiza o conteúdo em segundo plano.
- Share target permite que seu app receba conteúdo compartilhado do sistema operacional.
Cada um desses recursos exige a permissão do usuário e deve ser usado com moderação, mas são eles que fazem um PWA parecer um app nativo de primeira classe.
Melhores práticas
- Sirva via HTTPS e registre o worker após o carregamento.
- Versione os caches e exclua os antigos no evento
activate. - Use a estratégia cache-first para assets com hash e network-first para HTML.
- Sempre forneça um fallback para modo offline.
- Faça o precache apenas do app shell, não do site inteiro.
- Considere usar o Workbox em vez de escrever estratégias complexas manualmente.
- Teste o modo offline e as atualizações antes de fazer o deploy.
Erros comuns
- Fazer cache de HTML para sempre e servir páginas desatualizadas.
- Esquecer de limpar caches antigos após um deploy.
- Fazer cache de respostas cross-origin opacas sem entender suas limitações.
- Fazer precaching de tudo e inflar a primeira instalação.
- Presumir que o novo worker é ativado imediatamente.
- Ignorar a cota de armazenamento e deixar os caches crescerem sem controle.
Próximos passos
Um service worker é o maior ganho individual de performance para visitas recorrentes e a base do suporte offline. Combine-o com as técnicas do guia de Web Performance, entenda as requisições HTTP que ele intercepta e sirva o app a partir de um backend Node.js. Depois, adicione um manifest e um worker simples de cache-first a um site existente e veja-o funcionar offline.