Guia de Migração RN para PWA

Guia completo do processo de migração do React Native para Progressive Web App

Índice

  1. Visão Geral
  2. Justificativa
  3. Arquitetura Proposta
  4. Etapas de Migração
  5. Implementações Específicas
  6. Considerações de Performance

Visão Geral

Estado Atual

Análise de Dependências Críticas

  1. Dependências Defasadas com Riscos de Segurança:

    • expo (versão ~49.0.0) - múltiplas vulnerabilidades reportadas
    • react-native-screens (~3.22.0) - versões antigas com falhas de memória
    • expo-sqlite (~11.3.2) - problemas conhecidos de corrupção de dados
    • react-native-reanimated (~3.3.0) - versão com memory leaks reportados
  2. Bibliotecas Descontinuadas:

    • react-native-expo-image-cache (sem manutenção ativa)
    • react-native-root-siblings (última atualização > 1 ano)
  3. Conflitos de Versões:

    • Múltiplas versões do @babel/core causando inconsistências
    • Dependências aninhadas com versões incompatíveis

Objetivo da Migração

  1. Unificar as bases de código em um PWA robusto utilizando Next.js
  2. Atualizar todas as dependências para versões LTS mais recentes
  3. Eliminar bibliotecas descontinuadas ou com vulnerabilidades conhecidas
  4. Implementar práticas modernas de segurança
  5. Manter todas as funcionalidades críticas

Benefícios de Segurança

  1. Eliminação de vulnerabilidades conhecidas em dependências antigas
  2. Implementação de CSP (Content Security Policy)
  3. Melhor proteção contra XSS através de sanitização moderna
  4. Armazenamento seguro de dados com IndexedDB
  5. Proteção contra CSRF em APIs

Justificativa

Por que PWA?

  1. Funcionamento offline robusto
  2. Base de código única
  3. Distribuição simplificada
  4. Atualizações instantâneas
  5. Menor custo de manutenção

Por que Next.js?

  1. Renderização híbrida (SSR/SSG/CSR)
  2. Otimização de performance
  3. Roteamento nativo
  4. API routes integradas
  5. Suporte nativo a PWA

Arquitetura Proposta

Estrutura de Diretórios

src/
├── components/
├── hooks/
├── modules/
│   ├── auth/
│   ├── orders/
│   ├── products/
│   └── sync/
├── pages/
│   ├── api/
│   └── [routes]/
├── services/
│   ├── database/
│   └── api/
├── store/
└── workers/
    └── service-worker.ts

Tecnologias Core

Etapas de Migração

1. Setup Inicial

# Criar novo projeto Next.js
npx create-next-app@latest --typescript

# Instalar dependências principais
pnpm add dexie workbox-window @tanstack/react-query zustand

2. Configuração do PWA

// next.config.js
const withPWA = require('next-pwa')({
  dest: 'public',
  register: true,
  skipWaiting: true,
  disable: process.env.NODE_ENV === 'development'
})

module.exports = withPWA({
  // outras configs
})

3. Banco de Dados Local

// services/database/connection.ts
import Dexie from 'dexie';

class AppDatabase extends Dexie {
  orders: Dexie.Table<OrderType, number>;
  products: Dexie.Table<ProductType, number>;
  customers: Dexie.Table<CustomerType, number>;

  constructor() {
    super('AppDatabase');
    this.version(1).stores({
      orders: '++id, orderId, status, customerId, syncStatus',
      products: '++id, code, name, price, stock',
      customers: '++id, code, name, status'
    });
  }
}

export const db = new AppDatabase();

4. Service Worker

// workers/service-worker.ts
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate, CacheFirst } from 'workbox-strategies';

// Cache estático
registerRoute(
  ({request}) => request.destination === 'style' ||
                 request.destination === 'script',
  new CacheFirst({
    cacheName: 'static-assets',
  })
);

// Cache de API
registerRoute(
  ({url}) => url.pathname.startsWith('/api/'),
  new StaleWhileRevalidate({
    cacheName: 'api-cache',
  })
);

// Sincronização em background
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-orders') {
    event.waitUntil(syncOrders());
  }
});

5. Sincronização

// services/sync/sync-service.ts
export class SyncService {
  async syncAll() {
    await this.syncOrders();
    await this.syncProducts();
    await this.syncCustomers();
  }

  private async syncOrders() {
    const pendingOrders = await db.orders
      .where('syncStatus')
      .equals('pending')
      .toArray();

    for (const order of pendingOrders) {
      try {
        await api.orders.sync(order);
        await db.orders.update(order.id, { syncStatus: 'synced' });
      } catch (error) {
        console.error('Sync failed:', error);
      }
    }
  }
}

Implementações Específicas

1. Autenticação

// modules/auth/hooks/useAuth.ts
export const useAuth = create((set) => ({
  user: null,
  token: null,
  login: async (credentials) => {
    const response = await api.auth.login(credentials);
    await db.user.put(response.user);
    set({ user: response.user, token: response.token });
  },
  logout: async () => {
    await db.user.clear();
    set({ user: null, token: null });
  }
}));

2. Gerenciamento de Estado Offline

// modules/orders/hooks/useOrders.ts
export const useOrders = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (order) => {
      // Salvar localmente primeiro
      await db.orders.put({
        ...order,
        syncStatus: 'pending'
      });

      // Tentar sincronizar se online
      if (navigator.onLine) {
        await syncService.syncOrders();
      }
    },
    onSuccess: () => {
      queryClient.invalidateQueries(['orders']);
    }
  });
};

3. Componentes Adaptados

// components/PDFViewer/index.tsx
import { useState, useEffect } from 'react';
import { Document, Page } from 'react-pdf';

export const PDFViewer = ({ url }) => {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);

  return (
    <div>
      <Document
        file={url}
        onLoadSuccess={({ numPages }) => setNumPages(numPages)}
      >
        <Page pageNumber={pageNumber} />
      </Document>
      <div>
        <button onClick={() => setPageNumber(p => Math.max(1, p - 1))}>
          Previous
        </button>
        <button onClick={() => setPageNumber(p => Math.min(numPages, p + 1))}>
          Next
        </button>
      </div>
    </div>
  );
};

Considerações de Performance

1. Otimização de Imagens

// next.config.js
module.exports = {
  images: {
    domains: ['seu-dominio.com'],
    deviceSizes: [640, 750, 828, 1080, 1200],
    imageSizes: [16, 32, 48, 64, 96],
    path: '/_next/image',
    loader: 'default'
  }
}

2. Code Splitting

// pages/orders/index.tsx
import dynamic from 'next/dynamic';

const OrderList = dynamic(() => import('@/components/OrderList'), {
  loading: () => <OrderListSkeleton />
});

3. Cache Strategies

// hooks/useCustomQuery.ts
export const useCustomQuery = (key, fetcher) => {
  return useQuery({
    queryKey: key,
    queryFn: fetcher,
    staleTime: 5 * 60 * 1000, // 5 minutos
    cacheTime: 30 * 60 * 1000, // 30 minutos
    retry: 3,
    retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000)
  });
};

Conclusão

A migração para um PWA com Next.js oferece:

  1. Base de código unificada
  2. Melhor experiência de desenvolvimento
  3. Performance otimizada
  4. Funcionamento offline robusto
  5. Distribuição simplificada

Próximos Passos

  1. Criar POC com funcionalidades principais
  2. Migrar módulos gradualmente
  3. Implementar testes E2E
  4. Realizar testes de performance
  5. Planejar estratégia de deploy

Apêndice

Comandos Úteis

# Desenvolvimento
pnpm dev

# Build de produção
pnpm build

# Análise de bundle
ANALYZE=true pnpm build

# Testes
pnpm test