Offline-First Frontend: Service Worker + Sync
App que funciona sem rede. Cache de assets e dados, fila de ações offline, sync ao reconectar.
Definição
Offline-first é uma estratégia onde o app funciona completamente sem conexão e sincroniza quando a rede volta. Componentes: Service Worker (intercepta requests, serve do cache), Cache API (armazena responses), Background Sync API (enfileira requests para quando a rede voltar), IndexedDB (dados estruturados para o app usar offline). PWA = Progressive Web App combina todos esses.
Problema → Solução
Problema
Usuário em metrô fecha o app. Volta com Wi-Fi. O carrinho que adicionou offline sumiu. Ou: rede instável faz o app travar em loading enquanto o usuário tem os dados em cache mas o app não usa.Solução
Estratégia Cache-first para assets estáticos (CSS, JS, imagens). Network-first com fallback para cache para dados dinâmicos. Background Sync para mutations offline (add ao carrinho, like, enviar mensagem). Workbox simplifica a implementação com recipes prontos. Next.js tem `next-pwa` plugin.Dica de entrevista
Tip
IndexedDB para dados estruturados offline é verboso — use Dexie.js como wrapper. Para sincronização com conflitos: implementar 'tombstone' (registro de deletes), timestamp de última modificação, e resolver conflitos no server-side na sync. O caso mais simples: queue de mutations com retry automático.Código
// next.config.ts com next-pwa
import withPWA from 'next-pwa'
export default withPWA({
dest: 'public',
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.*/,
handler: 'NetworkFirst',
options: { cacheName: 'api-cache', expiration: { maxAgeSeconds: 60 } }
},
{
urlPattern: /\.(js|css|png|jpg)$/,
handler: 'CacheFirst',
options: { cacheName: 'static-cache' }
}
]
})
// Fila de mutations offline com Dexie.js (IndexedDB wrapper)
import Dexie from 'dexie'
class OfflineDB extends Dexie {
pendingMutations!: Dexie.Table<{ id?: number; type: string; payload: any; ts: number }>
constructor() {
super('app-offline')
this.version(1).stores({ pendingMutations: '++id, type, ts' })
}
}
const db = new OfflineDB()
async function addToCart(item: CartItem) {
if (!navigator.onLine) {
// Salvar para sync posterior
await db.pendingMutations.add({ type: 'ADD_TO_CART', payload: item, ts: Date.now() })
return // UI optimistic já foi atualizada antes dessa chamada
}
await api.post('/cart', item)
}
// Sync ao voltar online
window.addEventListener('online', async () => {
const pending = await db.pendingMutations.toArray()
for (const mutation of pending) {
try {
await processMutation(mutation)
await db.pendingMutations.delete(mutation.id!)
} catch { break } // parar se falhar
}
})Perguntas de entrevista
Q.Quais estratégias de cache o Service Worker pode usar?▾
A.Cache Only (só cache, sem rede — assets imutáveis com hash no nome). Network Only (sempre rede — dados sensíveis). Cache First (cache → rede se miss — assets estáticos). Network First (rede → cache se offline — dados dinâmicos). Stale-While-Revalidate (cache imediato + rede em background para atualizar — melhor para dados que podem ter um delay). Workbox implementa todas como strategies prontos.
Q.Como a Background Sync API funciona?▾
A.Registrar um sync tag quando a ação falha por falta de rede: `navigator.serviceWorker.ready.then(sw => sw.sync.register('send-message'))`. O browser chama o evento sync no Service Worker quando a rede volta, mesmo se o app está fechado. O SW processa a fila salva no IndexedDB. Limitação: não é suportado no iOS/Safari — nesses casos, usar o evento online para disparar a sync manualmente.
Q.Como atualizar o Service Worker sem bugs de cache stale?▾
A.Cache busting: incluir hash no nome dos assets (webpack/Next.js fazem isso automaticamente). Ao deployar novo SW: ele instala mas fica waiting até todas as abas fecharem. Para forçar imediato: `self.skipWaiting()` no install + `clients.claim()` no activate. Versionamento de cache: ao ativar novo SW, deletar caches antigos pelo nome (incluir versão no nome do cache). Workbox tem `precacheAndRoute` que gerencia tudo.
Termos relacionados
118/122← →