Skip to main content

Modo offline (service worker + cache do TanStack Query)

O site abre e navega sem internet usando duas camadas independentes, ambas em apps/web/src/integrations/offline/: O SW nunca cacheia respostas da API: os dados vivem só no cache do Query. O useOfflineSupport (use-offline-support.ts, chamado no __root.tsx) liga as duas camadas.

Service worker (/sw.js)

Gerado no build (generateSW), só no ambiente client, e registrado só em produção. Atualiza sozinho (autoUpdate: skipWaiting + clientsClaim).
  • Assets (/assets/**, favicon.svg, manifest.json): precache, servidos do cache.
  • Navegações (HTML do SSR): network-first com timeout de 3 s, gravadas por URL no cache followw-pages-<build> (pages-cache.ts). Sem rede e sem cache para a URL, cai na página pai (/turmas/x → /turmas) ou na home, e o router resolve a rota no client.
  • Pré-carga: após abrir o app online, warmPages baixa /, /ru, /turmas e /perfil (ou /login, sem login) que ainda faltam. O cache de páginas é versionado por build, porque o HTML antigo aponta para assets que o SW novo já não tem; os caches de builds antigos são apagados.

Cache dos dados

  • persistQueryCache restaura o IndexedDB e passa a salvar cada mudança. Roda depois da hidratação, para o dado restaurado não divergir do HTML do SSR. Na restauração vence o dado mais novo (SSR ou salvo).
  • Validade: 14 dias (PERSIST_MAX_AGE: maxAge do persister e gcTime das queries no navegador). Query sem atualização há mais tempo deixa de ser salva (shouldPersistQuery); só o gcTime não bastaria, já que a restauração reinicia o prazo a cada sessão.
  • buster: hash de schema.gen.ts. Mudou o contrato da API, o cache salvo é descartado.
  • O cardápio é uma query por campus e semana (segunda a domingo); cada dia é um select dela. A semana do campus do usuário é pré-carregada (prefetchWeekMenus), salvo se já estiver no cache.

Nenhuma rota trava

  • Loaders usam loadQuery (src/queries/load.ts): com dado em cache resolvem na hora e revalidam em segundo plano; sem cache, esperam a rede.
  • Queries e mutações usam networkMode: 'always', e as queries não fazem retry offline: nunca ficam pausadas (o que travava o loader), nem se a conexão cair entre uma tentativa e outra.
  • As páginas só mostram erro quando não há dado (isLoadingError): falha ao revalidar mantém o que está salvo. isError só aparece onde há dado salvo para mostrar junto do aviso, e fica oculto offline.

Interface offline

  • OfflineBanner no AppLayout: “Você está offline”.
  • useFailureMessage (src/lib/online.ts) troca a mensagem de falha por OFFLINE_MESSAGE (“Você está sem conexão com a internet.”) quando não há conexão. O ErrorCard já faz isso sozinho.
  • Login mostra OFFLINE_MESSAGE e não envia sem conexão; logout fica desabilitado (o banner explica); a renovação da sessão espera a rede voltar.

Logout e sessão expirada

clearSession (e o login, que espera a limpeza terminar) remove todas as queries, exceto o /me (zerado em seguida), e chama clearOfflineData, que apaga o IndexedDB e o HTML em cache (que traz dados do SSR).

Testar

O SW só registra em produção. No DevTools: Application → Service workers e Network → Offline. A API local só aceita CORS de localhost:3000; para outra porta, suba com CORS_ORIGINS='["http://localhost:3100"]' uv run api.