R
Interview Deckreact · next.js · senior/staff
0·0·122
Performance

Bundle Size e Code Splitting

Analisar bundle, lazy loading de rotas e componentes, e tree shaking. JS que o usuário nunca vê.

Bundle size impacta diretamente o tempo de carregamento inicial. Cada KB extra de JS = mais download + parse + compile antes de qualquer interação. As principais estratégias: code splitting por rota (lazy + Suspense), dynamic imports para componentes pesados, tree shaking (eliminar código não usado), e análise de bundle para identificar os maiores culpados.

Problema
Bundle inicial com todo o código da app: páginas que o usuário pode nunca visitar, bibliotecas inteiras importadas quando só um método é usado (`import _ from 'lodash'` = 70KB), e componentes pesados (editores, gráficos) carregados na home.
Solução
React.lazy + Suspense para code splitting por rota e componente. Dynamic import para carregar sob demanda. Importações granulares: `import { debounce } from lodash-es` em vez de `import _ from lodash`. Analisar com @next/bundle-analyzer ou rollup-plugin-visualizer.
Tip
Metrica importante: First Load JS do Next.js (mostrado no build). Objetivo: < 100KB para rota inicial. Inspecione com `next build` — rotas marcadas com ○ (static) têm menor bundle. Dependências > 10KB sem uso crítico na home são candidatas a dynamic import.
// ✅ Code splitting por rota
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'

const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))

function App() {
  return (
    <Suspense fallback={<PageSkeleton />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  )
}

// ✅ Dynamic import de componente pesado
function Editor() {
  const [MonacoEditor, setMonaco] = useState(null)

  const loadEditor = async () => {
    const { default: Monaco } = await import('@monaco-editor/react')
    setMonaco(() => Monaco)
  }

  return MonacoEditor
    ? <MonacoEditor value={code} />
    : <button onClick={loadEditor}>Abrir editor</button>
}

// ✅ Importação granular (tree-shakeable)
import { debounce } from 'lodash-es' // ✅ só debounce
// import _ from 'lodash'            // ❌ 70KB inteiros
Q.Como funciona tree shaking?
A.Bundlers (Webpack, Rollup, Vite) analisam imports estáticos e eliminam código não referenciado. Funciona com ES modules (import/export). Não funciona com CommonJS (require). Por isso `import { debounce } from 'lodash-es'` (ES module) é tree-shakeable, mas `import _ from 'lodash'` (CJS) importa tudo. Verificar se a lib tem sideEffects: false no package.json.
Q.Qual a diferença entre lazy loading de rota e de componente?
A.Rota: `const Dashboard = lazy(() => import('./Dashboard'))` — carrega o chunk da rota só quando o usuário navega para ela. Componente: mesmo padrão mas para componentes pesados dentro de uma rota (ex: editor de texto, mapa, gráfico complexo). Ambos precisam de `<Suspense fallback={<Loading />}>` em volta.
Q.Como identificar o que está aumentando o bundle?
A.Next.js: `ANALYZE=true next build` com @next/bundle-analyzer — gera treemap visual. Vite: rollup-plugin-visualizer. Procurar: dependências grandes importadas inteiras, duplicação (mesma lib em versões diferentes), código de desenvolvimento incluído em prod. Ferramentas: bundlephobia.com para checar tamanho antes de instalar.
80/122← →