Guia de Migração RN para PWA
Guia completo do processo de migração do React Native para Progressive Web App
Índice
- Visão Geral
- Justificativa
- Arquitetura Proposta
- Etapas de Migração
- Implementações Específicas
- Considerações de Performance
Visão Geral
Estado Atual
- Aplicativo React Native (versão 0.72.4 - defasada)
- Versão web em React
- Foco em funcionamento offline
- SQLite para armazenamento local
- Sincronização com backend
Análise de Dependências Críticas
-
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
-
Bibliotecas Descontinuadas:
- react-native-expo-image-cache (sem manutenção ativa)
- react-native-root-siblings (última atualização > 1 ano)
-
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
- Unificar as bases de código em um PWA robusto utilizando Next.js
- Atualizar todas as dependências para versões LTS mais recentes
- Eliminar bibliotecas descontinuadas ou com vulnerabilidades conhecidas
- Implementar práticas modernas de segurança
- Manter todas as funcionalidades críticas
Benefícios de Segurança
- Eliminação de vulnerabilidades conhecidas em dependências antigas
- Implementação de CSP (Content Security Policy)
- Melhor proteção contra XSS através de sanitização moderna
- Armazenamento seguro de dados com IndexedDB
- Proteção contra CSRF em APIs
Justificativa
Por que PWA?
- Funcionamento offline robusto
- Base de código única
- Distribuição simplificada
- Atualizações instantâneas
- Menor custo de manutenção
Por que Next.js?
- Renderização híbrida (SSR/SSG/CSR)
- Otimização de performance
- Roteamento nativo
- API routes integradas
- 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
- Next.js 13+
- IndexedDB (via Dexie.js)
- Service Workers (via Workbox)
- TanStack Query
- Zustand
- Styled Components
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:
- Base de código unificada
- Melhor experiência de desenvolvimento
- Performance otimizada
- Funcionamento offline robusto
- Distribuição simplificada
Próximos Passos
- Criar POC com funcionalidades principais
- Migrar módulos gradualmente
- Implementar testes E2E
- Realizar testes de performance
- 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