Service workers: estratégias de cache offline-first
1. Fundamentos dos Service Workers e o Modelo Offline-First
Service Workers são scripts JavaScript que atuam como proxies entre o navegador e a rede, permitindo interceptar e gerenciar requisições HTTP. O modelo offline-first prioriza o conteúdo armazenado localmente, garantindo que aplicações funcionem mesmo sem conectividade.
O ciclo de vida do Service Worker possui três eventos principais:
- Instalação (
install): Momento ideal para pré-carregar assets críticos - Ativação (
activate): Oportunidade para limpar caches antigos - Requisições (
fetch): Interceptação e decisão sobre como responder
A interceptação de requisições permite implementar diferentes estratégias de cache. As três principais são:
- Cache-First: Prioriza o cache local, buscando rede apenas quando necessário
- Network-First: Tenta a rede primeiro, com fallback para cache
- Stale-While-Revalidate: Serve cache imediatamente e atualiza em segundo plano
// Registro básico de um Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => console.log('SW registrado:', registration.scope))
.catch(error => console.log('Erro no registro:', error));
}
2. Estratégia Cache-First (Cache Then Network)
A estratégia Cache-First é ideal para recursos estáticos que mudam raramente, como CSS, JavaScript, imagens e fontes. A resposta é servida instantaneamente do cache, reduzindo drasticamente a latência.
// Evento install: pré-cache de assets críticos
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1-static').then(cache => {
return cache.addAll([
'/',
'/styles/main.css',
'/scripts/app.js',
'/images/logo.png'
]);
})
);
});
// Estratégia Cache-First
self.addEventListener('fetch', event => {
if (event.request.url.includes('/static/')) {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
// Retorna do cache ou busca na rede
return cachedResponse || fetch(event.request).then(response => {
// Atualiza o cache em segundo plano
caches.open('v1-static').then(cache => {
cache.put(event.request, response.clone());
});
return response;
});
})
);
}
});
Casos de uso ideais:
- Assets de frameworks (React, Vue, Angular)
- Folhas de estilo e scripts de bibliotecas
- Imagens e fontes que não mudam frequentemente
3. Estratégia Network-First com Fallback para Cache
Para conteúdo dinâmico que precisa estar sempre atualizado, como páginas de notícias ou dados de API, a estratégia Network-First é mais adequada. A rede é tentada primeiro; se falhar, o cache serve como contingência.
// Estratégia Network-First
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
event.respondWith(
fetch(event.request)
.then(response => {
// Atualiza o cache com a resposta da rede
caches.open('v1-api').then(cache => {
cache.put(event.request, response.clone());
});
return response;
})
.catch(() => {
// Fallback para cache quando a rede falha
return caches.match(event.request).then(cached => {
if (cached) return cached;
// Resposta padrão para quando não há cache nem rede
return new Response(JSON.stringify({ error: 'Offline' }), {
status: 503,
headers: { 'Content-Type': 'application/json' }
});
});
})
);
}
});
Aplicações recomendadas:
- Páginas de produtos com preços atualizados
- Feeds de redes sociais
- APIs RESTful com dados mutáveis
4. Stale-While-Revalidate: Equilíbrio entre Velocidade e Frescor
Esta estratégia oferece o melhor dos dois mundos: o usuário vê conteúdo instantaneamente do cache, enquanto o Service Worker atualiza o cache em segundo plano para a próxima visita.
// Estratégia Stale-While-Revalidate
self.addEventListener('fetch', event => {
if (event.request.url.includes('/posts/')) {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
// Inicia busca na rede em paralelo
const fetchPromise = fetch(event.request).then(networkResponse => {
// Atualiza o cache com a resposta da rede
caches.open('v1-posts').then(cache => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
}).catch(() => cachedResponse);
// Retorna cache imediatamente ou aguarda rede
return cachedResponse || fetchPromise;
})
);
}
});
Cenários recomendados:
- Listas de posts de blog
- Dashboards com dados semi-estáticos
- Galerias de imagens com lazy loading
5. Cache de Páginas HTML e Navegação Offline
Para aplicações que precisam funcionar completamente offline, é essencial pré-cachear páginas HTML inteiras durante a instalação.
// Pré-cache de páginas HTML
const CACHE_NAME = 'v1-pages';
const PAGES_TO_CACHE = [
'/',
'/about.html',
'/contact.html',
'/offline.html'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(PAGES_TO_CACHE);
})
);
});
// Estratégia App Shell com NavigationPreload
self.addEventListener('activate', event => {
event.waitUntil(
self.registration.navigationPreload.enable()
);
});
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
(async () => {
try {
const preloadResponse = await event.preloadResponse;
if (preloadResponse) return preloadResponse;
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match('/offline.html');
return cachedResponse || new Response('Offline', { status: 503 });
}
})()
);
}
});
6. Gerenciamento de Cache e Limpeza Inteligente
Manter caches atualizados é crucial para evitar problemas de armazenamento e garantir que usuários recebam conteúdo recente.
// Versionamento e limpeza de caches antigos
const CACHE_VERSION = 'v2';
const STATIC_CACHE = `${CACHE_VERSION}-static`;
const DYNAMIC_CACHE = `${CACHE_VERSION}-dynamic`;
self.addEventListener('activate', event => {
const expectedCaches = [STATIC_CACHE, DYNAMIC_CACHE];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (!expectedCaches.includes(cacheName)) {
console.log('Removendo cache antigo:', cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
// Estratégia de expurgo LRU para cache dinâmico
async function limitCacheSize(cacheName, maxItems = 50) {
const cache = await caches.open(cacheName);
const keys = await cache.keys();
if (keys.length > maxItems) {
// Remove os itens mais antigos
await cache.delete(keys[0]);
return limitCacheSize(cacheName, maxItems);
}
}
Ferramentas de depuração:
- Chrome DevTools > Application > Cache Storage
- Workbox (biblioteca do Google para Service Workers)
- Lighthouse (auditoria de performance PWA)
7. Boas Práticas e Armadilhas Comuns
Evitar erros comuns:
- Cache excessivo de APIs mutáveis: Não armazene em cache requisições POST, PUT ou DELETE
- Escopo incorreto: O Service Worker só intercepta requisições dentro do seu escopo de path
- Ignorar headers de cache: Respeite
Cache-ControleETagdo servidor
// Boas práticas: ignorar métodos mutáveis
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
// Respeitar headers de cache do servidor
if (event.request.headers.get('Cache-Control')?.includes('no-store')) {
event.respondWith(fetch(event.request));
return;
}
// Aplicar estratégia de cache
event.respondWith(cacheFirst(event.request));
});
Testes offline:
- Chrome DevTools > Network > Offline
- Simular latência e desconexão
- Testar diferentes estratégias com Workbox
// Exemplo com Workbox (simplificado)
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
registerRoute(
/\/api\/posts\//,
new StaleWhileRevalidate({
cacheName: 'posts-cache'
})
);
Referências
- Service Workers: Uma Introdução (MDN Web Docs) — Documentação oficial sobre ciclo de vida, registro e eventos de Service Workers
- Estratégias de Cache com Service Workers (Google Developers) — Guia completo com exemplos práticos das principais estratégias de cache offline-first
- Workbox: Bibliotecas para Service Workers (Chrome Developers) — Conjunto de ferramentas do Google para simplificar implementação de estratégias de cache
- Navigation Preload com Service Workers (W3C) — Especificação técnica sobre pré-carregamento de navegação para reduzir latência
- Cache Storage API (MDN Web Docs) — Documentação detalhada sobre gerenciamento de caches, versionamento e limpeza
- Debugging Service Workers (Chrome DevTools) — Guia prático para depuração de Service Workers no Chrome DevTools