Barrel Exports vs Importaciones Directas en Next.js (con datos reales)

10 min de lectura
Next.jsPerformanceArchitecture

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ás

La 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:

ComponenteDetalle
Monoreponpm workspaces
Paquete compartidopackages/ui — 88 módulos
App barrelNext.js 15 — importa desde @repo/ui (barrel)
App directaNext.js 15 — importa desde paths específicos
Módulos usados9 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étricaBarrelDirectoDiferencia
First Load JS155 kB106 kB+49 kB (+46%)
Page chunk (sin comprimir)52.9 kB3.75 kB+49.15 kB (+1311%)
Total JS Chunks1,016 KB808 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?

EscenarioImpacto
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ónBeneficioRiesgo
sideEffects: falseTree shaking funcionaPuede romper tu app silenciosamente si algún módulo sí tiene side effects
optimizePackageImportsRewrites automáticos en Next.jsSolo 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:

EquipoCambioResultado
Atlassian (Jira, Confluence)Eliminar barrels75% builds más rápidos, 88% menos tests afectados
Joshua Goldberg (autor de typescript-eslint)Barrel → directo4.75 MB → 1.62 MB (66%), TBT 950 ms → 350 ms
Pascal Schilp (maintainer de MSW)Eliminar barrels179 → 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-exports

Es 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

Alexis Muñoz

Alexis Muñoz

Software Engineer