Truques para otimizar bundle size em aplicações React
1. Análise e Diagnóstico do Bundle
Antes de otimizar, é preciso medir. Ferramentas como webpack-bundle-analyzer e source-map-explorer revelam exatamente o que está ocupando espaço no bundle final.
Exemplo prático com webpack-bundle-analyzer:
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [new BundleAnalyzerPlugin()]
};
Para monitoramento contínuo, integre bundlesize no CI:
// package.json
"bundlesize": [
{ "path": "./build/static/js/main-*.js", "maxSize": "200 kB" }
]
O Lighthouse CI também fornece alertas automáticos quando o bundle ultrapassa limites definidos.
2. Code Splitting e Lazy Loading Estratégico
Dividir o código por rotas é a técnica mais eficaz. Use React.lazy() com Suspense:
import React, { Suspense } from 'react';
const Dashboard = React.lazy(() => import('./Dashboard'));
const Reports = React.lazy(() => import('./Reports'));
function App() {
return (
<Suspense fallback={<div>Carregando...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/reports" element={<Reports />} />
</Routes>
</Suspense>
);
}
Para bibliotecas pesadas, carregue sob demanda:
import { lazy } from 'react';
const HeavyChartLibrary = lazy(() =>
import(/* webpackChunkName: "chart-lib" */ 'heavy-chart-library')
);
3. Otimização de Dependências e Tree Shaking
Substitua bibliotecas monolíticas por alternativas modulares. O exemplo clássico é trocar moment.js (231 kB) por date-fns (apenas funções importadas):
// ❌ Antes - importa todo o moment.js
import moment from 'moment';
moment().format('DD/MM/YYYY');
// ✅ Depois - apenas a função necessária do date-fns
import { format } from 'date-fns';
format(new Date(), 'dd/MM/yyyy');
Configure sideEffects no package.json para habilitar tree shaking:
{
"name": "meu-app",
"sideEffects": false
}
Prefira importações seletivas do lodash:
// ❌ Importa toda a biblioteca
import _ from 'lodash';
_.debounce(fn, 300);
// ✅ Importa apenas a função necessária
import debounce from 'lodash/debounce';
// ou
import { debounce } from 'lodash-es';
4. Redução de CSS e Assets no Bundle
Integre PurgeCSS para remover CSS não utilizado:
// postcss.config.js
module.exports = {
plugins: [
require('@fullhuman/postcss-purgecss')({
content: ['./src/**/*.{js,jsx,ts,tsx}'],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
})
]
};
Transforme SVGs em componentes React inline para evitar requests adicionais:
// Icone.jsx
const Icone = () => (
<svg viewBox="0 0 24 24" width="24" height="24">
<path d="M12 2L2 7l10 5 10-5-10-5z" />
</svg>
);
Para imagens, use lazy loading com react-lazy-load-image-component:
import { LazyLoadImage } from 'react-lazy-load-image-component';
<LazyLoadImage
src="imagem-grande.jpg"
effect="blur"
width={800}
height={600}
/>
5. Configuração Avançada do Webpack e Vite
Otimize a minificação com TerserPlugin e compressão Brotli:
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: { compress: { drop_console: true } }
})
]
},
plugins: [
new CompressionPlugin({
algorithm: 'brotliCompress',
filename: '[path][base].br'
})
]
};
Configure splitChunks para agrupar dependências de terceiros:
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
Para micro-frontends, utilize Module Federation do Webpack 5:
const { ModuleFederationPlugin } = require('webpack').container;
new ModuleFederationPlugin({
name: 'app1',
remotes: {
app2: 'app2@http://localhost:3002/remoteEntry.js'
},
shared: ['react', 'react-dom']
});
6. Boas Práticas de Código para Bundle Enxuto
Evite barrel files (index.js que reexporta tudo):
// ❌ Barrel file que importa tudo
// components/index.js
export { Button } from './Button';
export { Card } from './Card';
export { Modal } from './Modal';
// ...50 componentes
// ✅ Importe diretamente
import { Button } from './components/Button';
Prefira constantes inline a bibliotecas externas para operações simples:
// ❌ Biblioteca externa para algo simples
import { isEmail } from 'validator';
isEmail('user@example.com');
// ✅ Função inline
const isEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
Configure browserslist moderno para reduzir polyfills:
// package.json
"browserslist": [
"last 2 versions",
"not dead",
"> 0.5%"
]
A otimização de bundle size é um processo contínuo. Monitore regularmente, estabeleça orçamentos de tamanho e automatize verificações no CI. Cada quilobyte economizado resulta em carregamento mais rápido e melhor experiência do usuário.
Referências
- Webpack Bundle Analyzer — Ferramenta interativa para visualizar o conteúdo do bundle e identificar módulos pesados
- React.lazy e Suspense (Documentação Oficial) — Guia oficial sobre code splitting com lazy loading em React
- PurgeCSS — Tutorial de integração do PurgeCSS com React para remover CSS não utilizado
- date-fns vs moment.js — Comparação de tamanho e performance entre bibliotecas de manipulação de datas
- Webpack SplitChunksPlugin — Documentação oficial sobre otimização de chunks e cache groups
- Lighthouse CI — Ferramenta do Google para monitoramento contínuo de performance e bundle size
- Browserslist — Configuração de alvos de navegadores para redução automática de polyfills