R
Interview Deckreact · next.js · senior/staff
0·0·122
System Design

Offline-First Frontend: Service Worker + Sync

App que funciona sem rede. Cache de assets e dados, fila de ações offline, sync ao reconectar.

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
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.
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.
// 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
  }
})
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.
118/122← →