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