📚 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:

  1. Estratégias de cache avançadas (Cache-First para assets estáticos e Stale-While-Revalidate para dados dinâmicos de produtos).
  2. Tela de fallback customizada (/offline) exibida automaticamente em caso de queda de conexão.
  3. 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:


🤔 Por que fizemos assim?


🔍 Checkpoint

  1. 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.
  2. Redirecionamento Fallback: Sem conexão ativa, clique em uma página não visitada previamente. O navegador deve exibir a rota /offline customizada 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.

Voltar para o Sumário