manualChunks y chunking avanzado
Cuando la extracción automática no agrupa como tu producto necesita, manualChunks te da el volante: decide a mano qué módulos van a qué chunk, en forma de objeto declarativo o de función sobre cada id. La herramienta para separar vendor, agrupar por ruta o por tamaño, y también la más fácil de usar mal.
El algoritmo automático de chunking es bueno, pero no conoce tu producto: no sabe que tu framework y tu librería de UI conviene agruparlos, ni que ese paquete gigante merece su propio chunk. manualChunks es la vía para imponer tu criterio sobre el del bundler, mapeando módulos a chunks con nombre. Es poder de precisión —y, como todo poder de precisión sobre un algoritmo que ya optimizaba solo, la forma más directa de romper lo que funcionaba.
- Configurar
manualChunksen sus dos formas: objeto declarativo y función sobre el id. - Aplicar las estrategias clásicas: separar vendor, agrupar por ruta y trocear por tamaño.
- Reconocer los riesgos: orden de carga, chunks huérfanos y ciclos entre chunks.
- Situar
manualChunksfrente aadvancedChunksde Rolldown en el toolchain de 2026.
Las dos formas de manualChunks
manualChunks vive en output de las opciones de Rollup, que Vite expone bajo build.rollupOptions. Su forma más simple es un objeto: mapeas un nombre de chunk a la lista de módulos que quieres dentro. Es declarativo y legible, ideal para agrupar dependencias concretas.
// vite.config.ts — forma objeto: nombre de chunk -> modulos
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
"vendor-react": ["react", "react-dom"],
"vendor-charts": ["d3", "visx"],
},
},
},
},
});
La forma función es más potente: recibe el id —la ruta absoluta— de cada módulo y devuelve el nombre del chunk donde debe ir, o nada para dejar que el algoritmo automático decida. Con ella expresas reglas dinámicas sobre rutas enteras, imposibles de enumerar a mano.
// forma funcion: una regla que clasifica cada modulo por su id
manualChunks(id) {
if (id.includes("node_modules")) {
if (id.includes("react")) return "vendor-react";
return "vendor";
}
if (id.includes("/src/rutas/admin/")) return "ruta-admin";
}
Un malentendido frecuente: manualChunks no divide código que no estuviera ya dividido, reorganiza cómo se agrupan los módulos en chunks. Las fronteras asíncronas siguen naciendo de tus import(). Lo que haces aquí es decidir, dentro de lo que ya se separa, qué módulos comparten archivo. Por eso es una herramienta de agrupación, no de corte: el corte lo pusiste tú con import() en el nivel anterior.
Estrategias de agrupación
Separar vendor
Todo node_modules a un chunk, o subdividido por familias grandes. La estrategia base para la caché de larga duración.
Agrupar por ruta
Reunir los módulos de una sección entera —un panel de administración— en un chunk, aunque el corte fino los dispersara.
Trocear por tamaño
Aislar una dependencia enorme en su propio chunk para que su hash no arrastre al resto, o fundir muchas minúsculas en una.
La estrategia por tamaño es la más sutil. Una dependencia muy grande merece su propio chunk porque, si cambia, no querrás invalidar nada más con ella; y a la inversa, una constelación de módulos diminutos conviene fundirlos, porque cada chunk tiene un coste fijo de petición que en exceso no compensa. manualChunks te deja implementar ambas políticas donde el automático no llega.
Cuándo tocar esto de verdad es la pregunta clave. La respuesta honesta en 2026: pocas veces. El automático agrupa por alcanzabilidad y acierta en la gran mayoría de los casos; las intervenciones que rinden son las que codifican conocimiento del producto que el bundler no puede deducir del grafo —que dos librerías se versionan al unísono, que una sección es rara y pesada, que un paquete enorme conviene aislar para proteger la caché del resto—. Si tu única justificación para una regla manual es “quería más chunks”, el automático probablemente ya tenía razón.
Los peligros del control manual
Aquí está la trampa. El algoritmo automático garantizaba dos cosas que tú, al tomar el volante, puedes romper sin darte cuenta: que no haya duplicación y que el orden de carga sea correcto.
Si fuerzas a un módulo a un chunk distinto del de sus dependientes, puedes crear una situación donde un chunk se ejecute antes de que su dependencia esté cargada. El bundler intenta ordenar las cargas con modulepreload, pero una agrupación manual incoherente puede producir referencias a código aún no evaluado. La regla: nunca separes un módulo de aquello que debe existir antes que él en el mismo instante de ejecución.
El segundo riesgo es la duplicación reintroducida: si mandas a mano un módulo compartido a un chunk concreto, puede que otro chunk que también lo necesitaba acabe con su propia copia, deshaciendo justo la optimización que el automático te daba gratis. Y el tercero son los ciclos entre chunks: agrupaciones que se importan mutuamente y que el bundler tiene que resolver con cargas encadenadas, empeorando la cascada que veremos en el próximo nivel.
flowchart TD subgraph Bien A1[entry] --> V1[vendor estable] A1 --> C1[compartido] end subgraph Mal A2[entry] --> X[grupo manual] X --> Y[otro grupo manual] Y --> X end style V1 fill:#a6e3a1,color:#11111b style X fill:#f38ba8,color:#11111b style Y fill:#f38ba8,color:#11111b
Un cuarto riesgo, más silencioso: una función manualChunks con estado externo o sensible al orden de visita puede asignar el mismo módulo a chunks distintos entre builds, produciendo hashes que cambian sin que cambie el código y envenenando la caché. Basa la regla solo en el id del módulo, sin efectos secundarios, para que el mismo código genere siempre los mismos chunks.
Rolldown y advancedChunks en 2026
Con Rolldown como motor de Vite 8, la API de chunking manual evoluciona. Rolldown ofrece advancedChunks, un sistema más expresivo que el manualChunks clásico: en lugar de un mapeo plano, defines grupos con condiciones —patrones sobre el id, umbrales de tamaño mínimo y máximo, número mínimo de módulos que comparten un chunk—. Es la respuesta a los casos donde la forma función se volvía un enredo de condicionales.
// Rolldown: advancedChunks con grupos y umbrales
export default defineConfig({
build: {
rollupOptions: {
output: {
advancedChunks: {
groups: [
{ name: "vendor", test: /node_modules/, minSize: 30000 },
{ name: "admin", test: /\/src\/rutas\/admin\// },
],
},
},
},
},
});
manualChunks sigue soportado por compatibilidad, pero la dirección de 2026 es declarar políticas con umbrales en vez de listas fijas: dile al bundler qué tamaño mínimo justifica un chunk propio y deja que él decida los detalles. Es control sin microgestión, el punto medio entre confiar del todo en el automático y reescribirlo a mano.
Sea cual sea la forma que uses, la regla de oro es verificar en el build de producción, no en desarrollo. En dev, Vite sirve módulos sin aplicar tu estrategia de chunks, así que el efecto de manualChunks o advancedChunks solo se manifiesta tras construir y previsualizar. Muchas configuraciones que parecían correctas resultan inertes —o contraproducentes— cuando por fin se miran en el artefacto real.
Que el build no dé error solo prueba que compiló, no que tu estrategia mejoró nada. El chunking manual falla en silencio: produce un artefacto válido pero peor. Tras cada cambio, abre el analizador y confirma tres cosas —que no reapareció ninguna duplicación, que ningún chunk cayó por debajo del umbral útil, y que el vendor no se llevó código de app—. Solo el grafo de salida delata estos problemas.
Un síntoma fiable de que te has pasado con manualChunks: avisos del bundler sobre chunks vacíos, o un aumento del número de peticiones sin mejora alguna del tiempo de arranque. Cuando aparezcan, la reacción correcta casi nunca es añadir más reglas para parchear el efecto, sino quitar reglas y devolverle el problema al automático, que rara vez producía esos síntomas por su cuenta. La configuración de chunking es de las pocas partes de un build donde menos código suele significar mejor resultado.
Hay una asimetría incómoda en manualChunks que conviene mirar de frente: cada regla manual que añades es una restricción que le quitas al algoritmo automático, y ese algoritmo estaba resolviendo, bien y gratis, un problema de partición de grafos que tú no quieres resolver a mano. Por eso la actitud correcta ante el chunking avanzado no es la del ingeniero que toma el control, sino la del que negocia: intervienes en el mínimo de puntos donde tu conocimiento del producto supera al del optimizador —sabes que estas dos librerías cambian juntas, que esta sección es rara y pesada, que aquel paquete gigante merece aislamiento— y dejas que el algoritmo gobierne todo lo demás. Cada regla de más es una oportunidad de reintroducir la duplicación que el automático eliminaba, de romper un orden de carga que respetaba, de crear un ciclo que no existía. La evolución de manualChunks a advancedChunks es exactamente el reconocimiento de esta asimetría: en vez de dictar listas fijas de módulos —imponiendo tu criterio módulo a módulo— declaras políticas con umbrales, que son restricciones más blandas, expresadas en el lenguaje del propio optimizador —tamaño mínimo, número de referencias— y por tanto mucho menos propensas a pelearse con él. La madurez en esta materia se mide al revés de lo que uno esperaría: no por cuánto chunking configuras, sino por cuán poco necesitas configurar para obtener la partición que tu producto reclama. Un archivo de configuración de chunking largo casi siempre delata, no un control experto, sino una lucha contra un algoritmo que habría hecho un trabajo mejor si lo hubieras dejado.
- Añade un
manualChunksen forma objeto para separar tu framework en unvendorpropio y confirma su hash independiente en el build. - Reescríbelo en forma función y agrupa por ruta una sección entera de tu app, verificando que sus módulos caen en el chunk esperado.
- Fuerza a propósito un módulo compartido a un chunk concreto y comprueba si reaparece duplicado en otro; deshaz el cambio si es así.
- Mide el número total de chunks y su tamaño antes y después de tus reglas: decide si ganaste caché o solo añadiste complejidad.
- Migra una de tus reglas a
advancedChunkscon unminSizey observa cómo el umbral evita crear chunks demasiado pequeños.