Barrel Exports vs Importaciones Directas en Next.js (con datos reales)
Al final de este artículo hay una skill instalable que detecta barrel files problemáticos en tu proyecto y sugiere imports directos. Pero primero, los datos: un solo index.ts re-exportando 88 módulos hizo el page chunk 14x más pesado. Armé un monorepo para medirlo. Esto es lo que encontré.
Qué es un barrel file (y por qué existe)
Un barrel file es un index.ts que re-exporta todo lo que hay en un directorio o paquete. El patrón es simple:
// packages/ui/src/index.ts (barrel)
export { Button } from './components/Button';
export { Input } from './components/Input';
export { useForm } from './hooks/useForm';
// ... 85 módulos másLa ventaja es un import limpio:
// Con barrel
import { Button } from '@repo/ui';
// Sin barrel (import directo)
import { Button } from '@repo/ui/components/Button';Existen por una razón válida: paths limpios, patrón facade, y una API pública clara para el paquete. El problema no es el patrón en sí.
El problema no es el patrón. Es lo que pasa cuando el bundler no puede hacer tree-shake de lo que no usás.
El experimento: monorepo con 88 módulos
Diseñé un monorepo con npm workspaces para aislar exactamente el impacto de los barrels. El setup:
| Componente | Detalle |
|---|---|
| Monorepo | npm workspaces |
| Paquete compartido | packages/ui — 88 módulos |
| App barrel | Next.js 15 — importa desde @repo/ui (barrel) |
| App directa | Next.js 15 — importa desde paths específicos |
| Módulos usados | 9 de 88+ disponibles |
El paquete packages/ui contiene 20 componentes, 30 íconos, 11 hooks, 18 utils y 6 archivos de constantes (~155 KB de código fuente). Diseñé los barrels deliberadamente con side effects — registros globales en módulos que contaminan el scope — y sin sideEffects: false en package.json. Ambas apps consumen exactamente los mismos 9 módulos.
Resultados: barrel vs directo
| Métrica | Barrel | Directo | Diferencia |
|---|---|---|---|
| First Load JS | 155 kB | 106 kB | +49 kB (+46%) |
| Page chunk (sin comprimir) | 52.9 kB | 3.75 kB | +49.15 kB (+1311%) |
| Total JS Chunks | 1,016 KB | 808 KB | +208 KB (+25.7%) |
El page chunk es donde duele. La app barrel mete 52.9 kB de JavaScript en el chunk de página, contra 3.75 kB del directo. Son 14 veces más código que el navegador tiene que parsear y ejecutar.
¿Qué significa en la práctica?
| Escenario | Impacto |
|---|---|
| Conexión 3G | +0.98 s de carga adicional |
| 10K visitas/día | ~14 GB de JS desperdiciado por mes |
El build time pasó de 8.3 s (directo) a 9.0 s (barrel) — +8.4%, diferencia menor, pero consistente.
El First Load JS es lo que todos tus usuarios descargan antes de ver algo. 49 kB extra en cada visita se acumula.
Por qué el tree shaking falla con barrels
Tree shaking es el proceso que usa el bundler para eliminar código muerto — exports que nadie importa — y así solo incluir en el bundle final el código que realmente usás.
Cuando importás { Button } desde un barrel, el bundler no puede simplemente tomar Button e irse. Tiene que evaluar el index.ts completo para resolver las dependencias. Esto significa cargar y analizar los 88 módulos, aunque solo uses 1.
Si algún módulo tiene side effects (código que se ejecuta solo al importar el archivo, como un registro global, un console.log, o una mutación de estado) el bundler no puede eliminarlo. No sabe si ese side effect es necesario para que tu app funcione.
Marvin Hagemeister (core team de Preact) midió el costo de escalar: 500 módulos tardan 0.15 s en resolverse, pero 10,000 tardan 3.12 s — el costo crece exponencialmente con la cantidad de re-exports.
Así se ve el barrel del experimento:
// packages/ui/src/index.ts
export { Button } from './components/Button';
export { Card } from './components/Card';
// ... 18 componentes más
export { IconHome } from './icons/IconHome';
// ... 29 íconos más
export { useForm } from './hooks/useForm';
// ... 10 hooks más
// Side effect: registro global
import { registerComponents } from './registry';
registerComponents();El registerComponents() es un side effect. El bundler ve eso y dice: "no puedo eliminar nada de este archivo porque no sé qué depende de ese registro".
Soluciones parciales
Hay dos configuraciones que mitigan el problema sin eliminar los barrels:
`sideEffects: false` en package.json le dice al bundler: "ningún archivo de este paquete ejecuta código al importarse". Con esa garantía, el bundler puede eliminar todo lo que no se importa directamente.
⚠ Cuidado
Pero si algún archivo sí tiene side effects (código que se ejecuta al importarse, como los registros globales que vimos antes), el bundler lo elimina igual — y tu app se rompe sin errores visibles. Usá esta configuración solo si estás seguro de que ningún módulo del paquete tiene side effects.
`optimizePackageImports` en next.config es la solución de Vercel para Next.js. Transforma automáticamente imports de barrels en imports directos durante el build. Según Vercel, esto logra builds 15-70% más rápidos en dev y 28% más rápidos en producción para paquetes como lucide-react y @mui/icons-material.
| Solución | Beneficio | Riesgo |
|---|---|---|
sideEffects: false | Tree shaking funciona | Puede romper tu app silenciosamente si algún módulo sí tiene side effects |
optimizePackageImports | Rewrites automáticos en Next.js | Solo funciona con Next.js, no cubre todo |
Ninguna configuración reemplaza una buena arquitectura de imports. Estas son parches, no soluciones.
Evidencia externa
Equipos grandes midieron lo mismo:
| Equipo | Cambio | Resultado |
|---|---|---|
| Atlassian (Jira, Confluence) | Eliminar barrels | 75% builds más rápidos, 88% menos tests afectados |
| Joshua Goldberg (autor de typescript-eslint) | Barrel → directo | 4.75 MB → 1.62 MB (66%), TBT 950 ms → 350 ms |
| Pascal Schilp (maintainer de MSW) | Eliminar barrels | 179 → 45 módulos (75% reducción) |
Pascal Schilp también creó eslint-plugin-barrel-files, una herramienta de ESLint para detectar y prevenir barrel files problemáticos en tu codebase.
Cuándo SÍ usar barrels
No es una condena absoluta. Los barrels funcionan bien cuando el paquete es chico, no tiene side effects, y el consumidor usa la mayoría de lo que exporta.
Si tenés un paquete con 5 componentes y sideEffects: false, un barrel es perfectamente razonable. El import limpio vale la pena cuando el costo es cero.
La regla práctica: si el consumidor usa menos del 30% de lo que el barrel exporta, el barrel es un problema. Si usa el 80%+, el barrel simplifica sin costo real.
Skill instalable: barrel-exports
Empaquetamos el conocimiento de este artículo en una Agent Skill que detecta barrel files problemáticos en tu proyecto, analiza cuántos módulos re-exportan vs cuántos se usan, y sugiere imports directos.
npx skills add alexismunoz1/barrel-exportsEs open source. Podés ver el código, contribuir o reportar issues en GitHub.
Un archivo index.ts puede parecer inofensivo. Los números dicen otra cosa.Fuentes
- How We Optimized Package Imports in Next.js — Vercel
- Speeding Up the JavaScript Ecosystem: The Barrel File Debacle — Marvin Hagemeister
- How We Achieved 75% Faster Builds by Removing Barrel Files — Atlassian
- Speeding Up Centered Part 3: Barrel Exports — Joshua Goldberg
- Barrel Files: A Case Study — Pascal Schilp
- eslint-plugin-barrel-files — GitHub