📚 Módulo 11: Mobile/PWA - Cache Offline com Service Workers, Tela Fallback e Sincronização
Com a infraestrutura de PWA inicializada no Módulo 10, daremos resiliência ao nosso e-commerce TecLoja 04. Um dos maiores benefícios de um PWA corporativo é continuar funcionando de forma graciosa mesmo quando o cliente está em trânsito e perde a conexão de internet (modo offline).
Neste módulo, implementaremos:
- Estratégias de cache avançadas (Cache-First para assets estáticos e Stale-While-Revalidate para dados dinâmicos de produtos).
- Tela de fallback customizada (
/offline) exibida automaticamente em caso de queda de conexão. - Persistência offline temporária das transações de carrinho no IndexedDB local, com sincronização automática com o backend NestJS ao recuperar a rede.
🗺️ 1. Ciclo de Caching e Roteamento Offline
Abaixo, descrevemos a árvore de decisões do Service Worker ao interceptar requisições HTTP em cenários com e sem sinal de internet:
flowchart TD
%% Styling
classDef sw fill:#FF9800,stroke:#E65100,stroke-width:2px,color:#fff;
classDef cache fill:#4CAF50,stroke:#2E7D32,stroke-width:2px,color:#fff;
classDef net fill:#2196F3,stroke:#0D47A1,stroke-width:2px,color:#fff;
A[Next.js Client Request] --> B[Service Worker Interceptor]:::sw
B --> C{Conexão Online?}:::sw
C -->|Sim| D[Buscar da Rede / API NestJS]:::net
D --> E[Atualizar Cópia no Cache Storage]:::cache
C -->|Não| F{Recurso no Cache?}:::cache
F -->|Sim| G[Servir do Cache Storage]:::cache
F -->|Não| H[Redirecionar para Rota /offline]:::sw
⚡ 2. Estratégias de Cache no Next.js (next.config.js)
Para customizar a estratégia de cacheamento da API NestJS e das páginas do catálogo, configuraremos rotas personalizadas com regras do Workbox no plugin de PWA.
Abra o arquivo next.config.js no frontend:
const withPWA = require("@ducanh2912/next-pwa").default({
dest: "public",
disable: process.env.NODE_ENV === "development",
workboxOptions: {
runtimeCaching: [
{
// 1. Imagens e assets estáticos (Cache-First)
urlPattern: /\.(?:png|jpg|jpeg|svg|webp|gif|ico)$/i,
handler: "CacheFirst",
options: {
cacheName: "tecloja-static-assets",
expiration: {
maxEntries: 100,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Dias
},
},
},
{
// 2. Chamadas de API NestJS (Stale-While-Revalidate)
urlPattern: /^https:\/\/.*\.neon\.tech\/api\/.*$/i, // Ajuste para a URL de sua API
handler: "StaleWhileRevalidate",
options: {
cacheName: "tecloja-api-data",
expiration: {
maxEntries: 50,
maxAgeSeconds: 5 * 60, // 5 minutos de tolerância sem rede
},
},
}
],
},
});
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = withPWA(nextConfig);
📴 3. Criação de Tela Offline Customizada (/app/offline/page.tsx)
Se o usuário tentar navegar para uma tela não cacheada enquanto estiver desconectado, exibiremos uma página elegante de fallback com explicações, prevenindo que o navegador renderize a clássica tela de dinossauro de erro de conexão.
Crie o arquivo /app/offline/page.tsx:
import Link from 'next/link';
export default function OfflinePage() {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-950 text-slate-100 p-6">
<div className="text-center max-w-md bg-slate-900 border border-slate-800 rounded-2xl p-8 shadow-2xl">
<span className="text-6xl mb-4 block">📡</span>
<h1 className="text-2xl font-bold text-sky-400 mb-2">Você está sem conexão</h1>
<p className="text-slate-400 mb-6">
A TecLoja 04 continua de pé! Mas, para visualizar esta página ou concluir transações em tempo real, precisamos que você se reconecte à internet.
</p>
<Link
href="/"
className="inline-block bg-sky-500 hover:bg-sky-600 text-slate-950 font-bold px-6 py-3 rounded-lg transition-colors"
>
Tentar Novamente
</Link>
</div>
</div>
);
}
🔄 4. Persistência e Sincronização de Pedidos com IndexedDB
Para compras iniciadas offline, salvaremos a transação no banco de dados local do navegador (IndexedDB) e tentaremos reprocessar o pedido de forma silenciosa assim que a rede for restabelecida.
Implementação da Sincronização em um Componente (/components/SyncManager.tsx)
Crie este gerenciador global de sincronização e insira-o no seu layout principal:
'use client';
import { useEffect } from 'react';
import axios from 'axios';
export default function SyncManager() {
useEffect(() => {
const realizarSincronizacao = async () => {
// 1. Recuperar transação salva localmente em caso de queda prévia
const pedidoPendente = localStorage.getItem('pedido_pendente_offline');
if (pedidoPendente && navigator.onLine) {
try {
const dadosPedido = JSON.parse(pedidoPendente);
// Dispara chamada para a API NestJS
await axios.post('http://localhost:3000/api/pedidos', dadosPedido, {
headers: {
Authorization: `Bearer ${localStorage.getItem('auth_token')}`
}
});
alert('Oba! Seu pedido que foi realizado offline acabou de ser faturado com sucesso! 🎉');
localStorage.removeItem('pedido_pendente_offline');
} catch (error) {
console.error("Falha ao sincronizar transação pendente:", error);
}
}
};
// Ouvir eventos nativos de conectividade do navegador
window.addEventListener('online', realizarSincronizacao);
// Executar verificação na inicialização caso já tenha voltado online
realizarSincronizacao();
return () => {
window.removeEventListener('online', realizarSincronizacao);
};
}, []);
return null; // Componente lógico, não renderiza nada na interface
}
✅ Pré-Requisitos deste Módulo
Antes de simular o comportamento offline, certifique-se de que:
- Você gerou a build de produção local com o arquivo
next.config.jsadaptado para caching. - Você configurou o fluxo de login JWT no e-commerce (Módulo 07).
🤔 Por que fizemos assim?
- Por que usar Stale-While-Revalidate para a lista de produtos? Em e-commerce de eletrônicos, preços e níveis de estoque oscilam constantemente. Usar cache estrito de longa duração (Cache-First) faria o aluno visualizar preços desatualizados. A estratégia
Stale-While-Revalidateserve os dados do cache de forma instantânea para dar agilidade visual e dispara uma requisição em segundo plano para atualizar o banco local com os dados mais recentes do NestJS, equilibrando performance e precisão. - Por que ouvir o evento
window.onlineno cliente? Para garantir a sincronização automatizada transparente ao usuário. Em vez de exigir que o aluno atualize manualmente a página para faturar seu carrinho pendente, a escuta nativa dispara o processamento em background no instante em que o sistema operacional do celular detecta a volta de rede celular ou sinal Wi-Fi.
🔍 Checkpoint
- Simulação de Desconexão: Abra a aplicação no navegador Chrome. Abra o console do DevTools (F12 -> Network), mude o seletor de rede de No Throttling para Offline. Atualize a página de produtos. O catálogo deve carregar instantaneamente buscando dados em cache.
- Redirecionamento Fallback: Sem conexão ativa, clique em uma página não visitada previamente. O navegador deve exibir a rota
/offlinecustomizada com sucesso.
⚠️ Erros Comuns
| Erro | Causa | Solução |
|---|---|---|
| A listagem de produtos falha e exibe tela em branco no teste offline | A rota da API NestJS ou banco local SQLite não coincide com a expressão regular declarada no urlPattern do arquivo de configuração. |
Verifique se as expressões regulares no urlPattern do next.config.js casam exatamente com o prefixo da sua API e porta de escuta. |
| Os dados em cache do PWA persistem mesmo atualizando o banco de dados | A estratégia de cache de API está configurada com duração de expiração muito longa ou sem revalidação em background. | Force o uso de StaleWhileRevalidate ou diminua o parâmetro maxAgeSeconds das rotas dinâmicas da API. |
| O componente de sincronização lança erros de SSR | window e navigator são APIs nativas do navegador e não existem no contexto de renderização do servidor Next.js. |
Certifique-se de colocar a diretiva 'use client' no topo do arquivo do componente de sincronização para forçar a execução isolada no cliente. |