Busca en todas las páginas de la documentación
// Step 1: Install and configure bundle analyzer
// npm install -D @next/bundle-analyzer
// next.config.ts
import withBundleAnalyzer from "@next/bundle-analyzer";
const nextConfig = {
// your config
};
export default withBundleAnalyzer({
enabled: process.env.ANALYZE === "true",
})(nextConfig);
// Step 2: Analyze
// ANALYZE=true npm run build
// Opens treemap showing every module and its size
// Step 3: Dynamic import for heavy client components
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("@/components/Chart"), {
loading: () => <div className="h-64 animate-pulse bg-gray-100 rounded" />,
ssr: false, // Skip SSR for client-only components
});
// Step 4: Named imports for tree-shaking
import { format } from "date-fns"; // 4KB — tree-shakes
// NOT: import dateUtils from "date-fns"; // 72KB — imports everythingCuándo usarlo: Cuando tu landing page carga más de 150KB gzipped de JavaScript, cuando tu app shell excede 300KB gzipped, o cuando Lighthouse marca "Reduce unused JavaScript."
// ---- BEFORE: Bloated bundle — 487KB gzipped client JS ----
// page.tsx — everything loaded eagerly
import { Chart } from "chart.js/auto"; // +180KB gzipped
import moment from "moment"; // +72KB gzipped
import _ from "lodash"; // +71KB gzipped
import { Editor } from "@monaco-editor/react"; // +120KB gzipped
Lo que esto demuestra:
moment (72KB) reemplazado con importación nombrada date-fns (4KB) — 68KB de ahorrolodash (71KB) reemplazado con .sort() nativo — 71KB de ahorroframer-motion reemplazado con animación CSS — 44KB de ahorroclient (navegador), server (Node.js), y edge. Enfócate en el bundle de cliente ya que afecta el rendimiento del lado del usuario.next/dynamic crean chunks separados que se cargan bajo demanda. El componente no está incluido en el bundle inicial y se obtiene cuando se renderiza por primera vez. El componente loading se muestra mientras se carga el chunk.import { x } from "mod" pero no con CommonJS require(). Las importaciones nombradas permiten al bundler analizar estáticamente qué exportaciones se usan.index.ts que re-exporta de múltiples módulos) pueden derrotar tree-shaking si el bundler no puede probar que no hay efectos secundarios. El campo sideEffects: false en package.json ayuda, pero evitar barrel files para librerías grandes es más seguro.(marketing) y (app) crean bundles separados, asegurando que páginas de marketing no carguen código específico de la aplicación.React.lazy para proyectos que no son Next.js:
import { lazy, Suspense } from "react";
const HeavyChart = lazy(() => import("./HeavyChart"));
function Dashboard() {
return (
<Suspense fallback={<div className="h-64 animate-pulse bg-gray-100"
Importación dinámica condicional basada en viewport:
"use client";
import dynamic from "next/dynamic";
import { useInView } from "react-intersection-observer";
const HeavyWidget = dynamic(() => import("@/components/HeavyWidget"), {
ssr: false,
});
function LazySection
Analizando costos de dependencias específicas:
# Check the cost of any npm package before adding it
npx bundle-phobia-cli lodash
# lodash: 71.5KB minified, 25.3KB gzipped
# Alternative: use the bundlephobia.com website
# https://bundlephobia.com/package/lodashReemplazos comunes de librerías pesadas para reducir tamaño de bundle:
| Librería Pesada | Tamaño (gzipped) | Alternativa más ligera | Tamaño (gzipped) | Ahorro |
|---|---|---|---|---|
| moment | 72KB | date-fns (importaciones nombradas) | 4KB | 68KB |
| lodash | 25KB | lodash-es (importaciones nombradas) o JavaScript nativo | 0-2KB | 23KB+ |
| chart.js | 65KB | lightweight-charts o importación dinámica | 0KB inicial | 65KB diferido |
| framer-motion | 44KB | Animaciones CSS o LazyMotion | 0-5KB | 39KB+ |
| axios | 13KB | fetch nativo | 0KB | 13KB |
dynamic(() => import("./Component")) deduce los props del componente de la exportación por defecto del módulo importado.dynamic(() => import("./module").then((m) => m.NamedComponent)).loading recibe { error, isLoading, pastDelay } para personalización.Las importaciones dinámicas agregan solicitudes de red — Cada componente importado dinámicamente se convierte en una solicitud HTTP separada. Demasiadas importaciones dinámicas en una sola página pueden crear una cascada de solicitudes. Solución: Agrupa componentes relacionados en un chunk dinámico único, o usa prefetching.
SSR: false oculta contenido de crawlers — Los componentes con ssr: false son invisibles para crawlers de motores de búsqueda y durante la representación inicial de HTML. Solución: Solo usa ssr: false para componentes verdaderamente interactivos (editores, canvas) que no pueden renderizarse en el servidor.
Tree-shaking requiere módulos ES — Las librerías CommonJS (require/module.exports) no pueden ser tree-shaken. Solución: Usa la variante de módulo ES cuando esté disponible (lodash-es en lugar de lodash, date-fns en lugar de moment).
Re-exportaciones de barrel file — Un index.ts que hace export * from "./heavy-module" fuerza al bundler a incluir el módulo completo incluso si solo importas una función. Solución: Importa directamente del archivo fuente: import { fn } from "./lib/specific-module" en lugar de import { fn } from "./lib".
El análisis de bundle solo muestra tamaño sin comprimir — El treemap muestra tamaños de módulo sin procesar. El tamaño real de transferencia depende de compresión gzip/brotli. El código densamente textual se comprime bien; el código binario o minificado no. Solución: Verifica tanto el treemap como la pestaña Network para tamaños reales de transferencia.
Code splitting prematuro — Dividir un componente de 5KB en una importación dinámica agrega complejidad y una solicitud de red para ahorros mínimos. Solución: Solo importa dinámicamente componentes que sean al menos 30KB gzipped o que estén detrás de interacción del usuario (modales, editores, paneles de configuración).
| Enfoque | Compromiso |
|---|---|
next/dynamic | Incorporado en Next.js; maneja SSR, estados de carga; solo Next.js |
React.lazy + Suspense | Agnóstico del framework; sin soporte SSR sin configuración adicional |
| Route-based splitting | Automático en Next.js; sin control por componente |
| Server Components | Elimina client JS completamente para componentes no interactivos |
| Module federation | Comparte código entre micro-frontends; setup complejo |
| Import maps | Resolución de módulos nativa del navegador; soporte de navegador limitado |
Instala @next/bundle-analyzer, agrégalo a next.config.ts, y ejecuta:
ANALYZE=true npm run buildEsto abre un treemap interactivo mostrando cada módulo y su tamaño a través de bundles de cliente, servidor, y edge. Enfócate en el bundle de cliente.
next/dynamic: incorporado en Next.js, maneja SSR, proporciona opciones loading y ssrReact.lazy + Suspense: agnóstico del framework, sin soporte SSR sin setup adicionalUsa next/dynamic en proyectos Next.js; usa React.lazy en apps React que no son Next.js.
Solo para componentes verdaderamente interactivos y solo-de-cliente como editores de canvas, editores de código, o widgets de mapas que no pueden renderizarse en el servidor. Los componentes con ssr: false son invisibles para crawlers de motores de búsqueda y durante la representación inicial de HTML.
Un index.ts que hace export * from "./heavy-module" fuerza al bundler a incluir el módulo completo incluso si solo usas una función.
Solución: Importa directamente del archivo fuente:
import { fn } from "./lib/specific-module"; // Bueno
import { fn } from "./lib"; // MaloDividir un componente menor a 30KB agrega complejidad y una solicitud de red para ahorros mínimos. Solo importa dinámicamente componentes que sean al menos 30KB gzipped o detrás de interacción del usuario (modales, editores, paneles de configuración).
Tree-shaking requiere sintaxis de módulo ES import/export para análisis estático. CommonJS require/module.exports es dinámico y no puede ser analizado estáticamente.
Solución: Usa variantes de módulo ES: lodash-es en lugar de lodash, date-fns en lugar de moment.
moment (72KB) -> date-fns importaciones nombradas (4KB)lodash (25KB) -> lodash-es importaciones nombradas o JavaScript nativo (0-2KB)axios (13KB) -> fetch nativo (0KB)framer-motion (44KB) -> Animaciones CSS o LazyMotion (0-5KB)Next.js automáticamente crea un chunk separado para cada página. Route groups como (marketing) y (app) crean bundles separados, asegurando que páginas de marketing no carguen código específico de la aplicación. Sin configuración necesaria.
const Editor = dynamic(
() => import("@monaco-editor/react").then((m) => m.Editor),
{ ssr: false }
);TypeScript deduce los props del componente de la exportación nombrada del módulo importado.
import dynamic from "next/dynamic";
import { useInView } from "react-intersection-observer";
const HeavyWidget = dynamic(() => import("./HeavyWidget"), { ssr: false });
function LazySection() {
const {
El treemap muestra tamaños de módulo sin procesar (sin comprimir). El tamaño real de transferencia depende de compresión gzip/brotli. Verifica la pestaña Network en DevTools para tamaños reales de transferencia. El código densamente textual se comprime bien; el código binario o minificado no.