O que é Code Splitting em React
Code splitting é uma estratégia de otimização que divide seu bundle JavaScript em partes menores, carregando apenas o código necessário quando é realmente necessário. Em aplicações React, isso significa não enviar todo o código JavaScript para o navegador do usuário na primeira requisição. Em vez disso, você separa o código em chunks menores que são carregados sob demanda.
Este conceito é fundamental para melhorar a performance, especialmente em aplicações grandes. Um bundle monolítico força o usuário a baixar, fazer parse e executar código que pode não ser usado naquele momento. Quando você implementa code splitting, o navegador baixa menos JavaScript inicialmente, acelerando o Time to Interactive (TTI) — métrica crítica para experiência do usuário. A diferença entre carregar 500KB versus 150KB no load inicial é significativa em conexões 3G ou em dispositivos mobile.
React.lazy() e Suspense: O Padrão Moderno
Entendendo React.lazy()
React.lazy() é uma função que permite importar componentes dinamicamente. Ela recebe um callback que retorna uma promise dynamic import e retorna um componente React que pode ser renderizado normalmente. O componente será carregado apenas quando for necessário renderizá-lo.
import React, { lazy } from 'react';
// Importação estática tradicional (evite para componentes pesados)
// import Dashboard from './pages/Dashboard';
// Importação dinâmica com lazy()
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<div>
<Dashboard />
</div>
);
}
export default App;
O Papel do Suspense
Quando você renderiza um componente lazy, ele começa a carregar o código. Durante o carregamento, o componente não está pronto para renderizar. É aí que Suspense entra em ação. Ele funciona como um limite que aguarda o carregamento do componente lazy e exibe um fallback (UI temporária) enquanto isso acontece.
import React, { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function LoadingSpinner() {
return <div style={{ padding: '20px', textAlign: 'center' }}>Carregando...</div>;
}
function App() {
const [page, setPage] = React.useState('dashboard');
return (
<div>
<button onClick={() => setPage('dashboard')}>Dashboard</button>
<button onClick={() => setPage('settings')}>Configurações</button>
<Suspense fallback={<LoadingSpinner />}>
{page === 'dashboard' && <Dashboard />}
{page === 'settings' && <Settings />}
</Suspense>
</div>
);
}
export default App;
No exemplo acima, quando o usuário clica em "Dashboard", o Suspense detecta que o componente não está carregado, exibe o LoadingSpinner e aguarda a chegada do código. Quando o bundle chega, o componente é renderizado automaticamente.
Dynamic Imports: A Base Técnica
Como Dynamic Imports Funcionam
Os dynamic imports (import()) são uma feature do JavaScript moderno que permite carregar módulos em tempo de execução. Quando você usa import() em vez de import tradicional, o bundler (Webpack, Vite, etc.) entende que aquele módulo deve ser separado em um chunk diferente.
// Importação estática - faz parte do bundle principal
import { getUserData } from './api/users';
// Importação dinâmica - carregada sob demanda
const getUserDataDynamic = () => import('./api/users');
// Uso
getUserDataDynamic().then(module => {
const { getUserData } = module;
getUserData(userId);
});
Tratamento de Erros em Dynamic Imports
É fundamental tratar erros ao trabalhar com carregamento dinâmico. A rede pode falhar, ou o usuário pode estar offline. Para isso, você pode envolver seu componente lazy com um Error Boundary.
import React, { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Erro ao carregar componente:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>Falha ao carregar o componente. Tente novamente.</div>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Carregando...</div>}>
<HeavyComponent />
</Suspense>
</ErrorBoundary>
);
}
export default App;
Casos de Uso Práticos e Estratégias
Code Splitting por Rota
A forma mais comum e eficaz de implementar code splitting é separar componentes por rota. Cada página tem seu próprio bundle, carregado apenas quando aquela rota é acessada.
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Blog = lazy(() => import('./pages/Blog'));
const BlogPost = lazy(() => import('./pages/BlogPost'));
function LoadingPage() {
return <div style={{ padding: '40px', textAlign: 'center' }}>Carregando página...</div>;
}
function App() {
return (
<Router>
<Suspense fallback={<LoadingPage />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:id" element={<BlogPost />} />
</Routes>
</Suspense>
</Router>
);
}
export default App;
Code Splitting para Componentes Pesados
Nem sempre é sobre rotas. Às vezes você tem um componente interno pesado que não é exibido imediatamente. Um editor de imagens, um gráfico complexo ou um modal avançado podem ser bons candidatos.
import React, { lazy, Suspense, useState } from 'react';
const ImageEditor = lazy(() => import('./components/ImageEditor'));
function PhotoApp() {
const [showEditor, setShowEditor] = useState(false);
return (
<div>
<button onClick={() => setShowEditor(true)}>Abrir Editor</button>
{showEditor && (
<Suspense fallback={<div>Carregando editor...</div>}>
<ImageEditor onClose={() => setShowEditor(false)} />
</Suspense>
)}
</div>
);
}
export default PhotoApp;
Preloading Estratégico
Às vezes você quer que o carregamento ocorra antecipadamente, antes do usuário realmente precisar. Por exemplo, ao passar o mouse sobre um link, você pode iniciar o carregamento.
import React, { lazy, Suspense } from 'react';
const ExpensiveModal = lazy(() => import('./ExpensiveModal'));
function PreloadExample() {
const [showModal, setShowModal] = React.useState(false);
const handleMouseEnter = () => {
// Inicia o carregamento sem renderizar
import('./ExpensiveModal');
};
return (
<div>
<button
onMouseEnter={handleMouseEnter}
onClick={() => setShowModal(true)}
>
Clique aqui
</button>
{showModal && (
<Suspense fallback={<div>Carregando...</div>}>
<ExpensiveModal onClose={() => setShowModal(false)} />
</Suspense>
)}
</div>
);
}
export default PreloadExample;
Medindo e Validando o Impacto
Analisando o Bundle
Use ferramentas como webpack-bundle-analyzer para visualizar o tamanho de cada chunk:
npm install --save-dev webpack-bundle-analyzer
Depois configure no seu webpack ou use com Create React App:
// react-app-rewired ou eject necessário
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
};
Métricas Importantes
O impacto real do code splitting é medido através de métricas:
- Initial Bundle Size: Tamanho do JavaScript carregado no primeiro acesso
- Time to Interactive (TTI): Tempo até a página ficar interativa
- First Contentful Paint (FCP): Tempo até conteúdo aparecer
Com code splitting bem implementado, você verá redução significativa no initial bundle, impactando diretamente em TTI e FCP. Um bundle reduzido em 60% pode resultar em 40-50% de melhoria no TTI em conexões lentas.
Conclusão
Code splitting é uma técnica indispensável em React moderno. Os três pontos fundamentais que você precisa reter:
-
React.lazy() + Suspense é o padrão recomendado pelo React para code splitting. Lazy carrega o componente, Suspense aguarda e exibe fallback enquanto isso acontece — é simples, declarativo e eficaz.
-
Separação por rota é o caso de uso mais comum e de maior impacto. Cada página da sua aplicação merece seu próprio bundle, reduzindo drasticamente o JavaScript inicial que os usuários precisam baixar.
-
Monitoramento é essencial. De nada adianta implementar code splitting se você não mede o impacto. Use ferramentas de análise de bundle e métricas reais (RUM) para validar que a experiência do usuário realmente melhorou.