perf: dividir o bundle por rota (−60% na carga inicial) - #38
Conversation
O frontend saia num chunk unico de 2,0 MB (597 kB gzip): quem abria a tela de login baixava a aplicacao inteira, incluindo o editor TipTap, os graficos e todas as telas de admin, para ver um formulario com dois campos. O Vite avisava disso a cada build. 41 rotas passam a entrar por lazy(), com um Suspense em volta. Login e Register ficam estaticos de proposito: sao o primeiro contato de quem chega sem sessao, e adiar essas duas trocaria o bundle grande por um flash de spinner logo na abertura. Carga inicial: 2400 kB -> 973 kB (649 kB -> 261 kB gzip), ~60% a menos. Medido no navegador: o login busca 26 chunks e o chunk do editor (570 kB, TipTap + ProseMirror + highlight.js, compartilhado por KBCreate e KBEdit) nao esta entre eles. Navegando para /forgot-password, o chunk proprio da rota e buscado sob demanda e a tela renderiza.
There was a problem hiding this comment.
Pull request overview
Divide o bundle do frontend por rota, migrando a maior parte das telas para carregamento sob demanda com React.lazy() + Suspense, reduzindo significativamente a carga inicial (especialmente para visitantes na tela de login).
Changes:
- Converte 41 rotas para carregamento lazy via
React.lazy(() => import(...)). - Envolve o bloco de
<Routes>em um<Suspense>com fallback de spinner consistente. - Mantém
LogineRegistercomo imports estáticos para preservar a experiência de primeira carga.
Suppressed comments (2)
frontend/src/App.jsx:64
- Comentário com palavras em português sem acentos ("nao", "aparencia", "ja"), o que parece erro ortográfico.
// Enquanto o chunk da rota chega. Usa o mesmo spinner das rotas
// protegidas, para a tela nao mudar de aparencia conforme o chunk ja
// esteja em cache ou nao.
frontend/src/App.jsx:16
- Comentário com palavra em português sem acento ("unico" → "único").
// O resto entra por rota. Antes tudo vinha num chunk unico de 2 MB, que
// todo visitante baixava inteiro para ver a tela de login.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Telas de entrada: estaticas de proposito. Sao o primeiro contato de | ||
| // quem chega sem sessao, e adiar o carregamento delas trocaria um bundle | ||
| // grande por um flash de spinner logo na abertura. |
O gatilho tinha filtro 'branches: [main]' no pull_request, entao um PR empilhado sobre outra branch nao recebia nenhum check - foi o que aconteceu com este proprio PR, aberto sobre feat/i18n-telas-secundarias para evitar conflito no App.jsx. Sem checks, a protecao da main so cobre o PR de baixo da pilha: o de cima entra sem ninguem ter rodado teste nele.
|
Adicionei um commit fora do escopo do PR, e quero deixar explícito o porquê: este PR estava sem nenhum check. O gatilho do CI tinha Tirei o filtro de branch de destino. Os três checks agora rodam aqui e passaram. |
perf: trazer o code splitting para a main (o #38 não chegou lá)
O problema
O frontend saía num chunk único de 2,0 MB (597 kB gzip). Quem abria a tela
de login baixava a aplicação inteira — o editor TipTap, os gráficos, todas as
telas de admin — para ver um formulário com dois campos. O Vite avisava disso a
cada build.
O que mudou
41 rotas passam a entrar por
lazy(), com umSuspenseem volta usando o mesmospinner que as rotas protegidas já usam (para a tela não mudar de aparência
conforme o chunk esteja em cache ou não).
Login e Register ficam estáticos de propósito. São o primeiro contato de
quem chega sem sessão; adiar essas duas trocaria um bundle grande por um flash
de spinner logo na abertura — pioraria a impressão em vez de melhorar.
Resultado
−60% no que o visitante baixa para ver a primeira tela.
Verificação no navegador
Não confiei só no relatório do build — medi a rede de verdade:
ProseMirror + highlight.js, compartilhado por KBCreate e KBEdit) não está
entre eles — que era exatamente o peso morto que motivou a mudança
/forgot-password, o chunkForgotPassword-*.jsé buscado sobdemanda (200) e a tela renderiza normalmente —
Suspenseelazyfuncionandoponta a ponta
npm test→ 7 passam ·vite buildok.Um detalhe do caminho
A primeira tentativa de reescrita falhou em silêncio: o
replacedo bloco<Routes>não casou (a função tinha ganhado o hook do i18n na linha anterior),mas o
replacedo fechamento casou — resultado, JSX desbalanceado. O buildpegou na hora, mas o aprendizado ficou: toda substituição de código agora passa
por asserção, em vez de falhar calada.