Inicio / Artículos

Ajuste de los nuevos controles de particionamiento de Turbopack en Next.js 16.3

Una explicación práctica de la nueva configuración turbopackChunking en Next.js 16.3, que detalla cómo maxChunkCountPerGroup y generateComponentChunks influyen en el tamaño del paquete y en el caché.

6 min de lectura1482 palabras

Eran las 4:40 de un viernes, justo en el momento en que se cerraba la computadora portátil para el fin de semana, cuando un despliegue rutinario de una aplicación de panel de control dejó de serlo silenciosamente. Una compilación que normalmente terminaba en 40 segundos ahora tardaba poco más de tres minutos, y el paquete del cliente para la ruta principal aumentó en casi 300 kb. Ninguna ruta había sido modificada. No se había incorporado ninguna nueva dependencia que pudiera explicarlo. El único cambio de esa semana fue un pequeño aumento en la versión de Next.js. Lo que siguió fue una tarde pasada mirando fijamente la pestaña de Red, observando cómo cuatro archivos masivos de JavaScript se cargaban uno tras otro, cuando antes doce archivos pequeños hacían el trabajo.

La causa no fue un error. Fue la lógica de fragmentación de Turbopack funcionando exactamente como estaba diseñada, solo que no de la manera esperada.

Por qué la fragmentación nunca se resolvió realmente

Este es un problema que los empaquetadores han estado abordando desde hace aproximadamente diez años. El SplitChunksPlugin de Webpack ofrecía parámetros como minSize, maxSize y cacheGroups, que casi nadie comprendía realmente desde sus principios fundamentales; en su mayoría se copiaron de algún hilo de Stack Overflow que data de 2019. El compromiso subyacente nunca desaparece: consolidar todo en menos bloques más grandes reduce las solicitudes HTTP, pero perjudica el caché, ya que un solo cambio en el CSS puede invalidar un paquete de 500 kb. Dividirlo en muchos bloques más pequeños mejora la granularidad del caché, pero multiplica las solicitudes de ida y vuelta, lo cual se vuelve evidente rápidamente en conexiones lentas.

Turbopack heredó esa misma tarea de equilibrio, y hasta hace poco tendía fuertemente hacia la consolidación. Esa es una opción razonable para sesiones de una sola página, donde la mayoría de los recursos se cargan una vez de todos modos. Sin embargo, resulta desfavorable si los usuarios navegan repetidamente por una aplicación grande, ya que el código previamente almacenado en caché sigue siendo descargado nuevamente junto con cualquier nuevo código que se encuentre cerca de él.

¿Qué cambió en 16.3?

Con Next.js 16.3, el comportamiento de fusión ya no está oculto dentro de una única heurística opaca, sino que ahora se gestiona directamente a través de la configuración. Se ha añadido un nuevo bloque turbopackChunking, y los dos parámetros más importantes son maxChunkCountPerGroup y maxMergeChunkSize.

// next.config.js
module.exports = {
  experimental: {
    turbopackChunking: {
      maxChunkCountPerGroup: 40, // default
      maxMergeChunkSize: 200_000, // bytes, default
    },
  },
};

Reducir el valor de maxChunkCountPerGroup hace que Turbopack adopte una estrategia de fusión más agresiva: se realizan menos solicitudes por carga de página, pero la retención en el caché es menor al navegar entre páginas. Aumentar este valor genera fragmentos más pequeños pero en mayor cantidad, que permanecen intactos incluso cuando cambian partes no relacionadas de la aplicación. maxMergeChunkSize establece un límite máximo para el tamaño de los fragmentos fusionados, evitando que Turbopack siga incluyendo código adicional en ellos; esto es útil cuando una sola dependencia grande del proveedor tiende a absorber todo lo demás.

Anteriormente no existían estos parámetros. Simplemente se utilizaba lo que determinaban las reglas predeterminadas y había que adaptarse a ello.

La opción de fragmentación de componentes

Una adición más importante es generateComponentChunks, que extiende la capacidad de fragmentación de Turbopack a los componentes individuales, en lugar de limitarse a las fronteras entre rutas.

module.exports = {
  experimental: {
    turbopackChunking: {
      generateComponentChunks: true,
    },
  },
};

Al activar esto, Turbopack genera tanto las versiones fusionadas como las no fusionadas de los chunks, permitiendo que el tiempo de ejecución decida en el momento de la carga qué versión obtener según lo que ya haya sido almacenado en caché por el navegador. Si un componente compartido fue guardado en caché de una visita anterior a la página, el tiempo de ejecución puede solicitar el chunk no fusionado y más pequeño en lugar de cargar un paquete fusionado que duplique código ya presente. Este es un enfoque fundamentalmente diferente a intentar calcular de antemano el tamaño ideal del chunk; se trata del primer caso en el que un bundler deja esa decisión para el momento de la solicitud en lugar de fijarla durante la compilación.

Lo que realmente vimos

Un equipo que probó esto en una aplicación de panel de control activó generateComponentChunks y observó qué ocurría durante los días siguientes. La carga inicial de la página apenas cambió, reduciéndose aproximadamente un 2%. Pero las navegaciones repetidas, que constituyen la mayor parte del uso real en una aplicación como un panel de control, provocaron que el tamaño promedio de los archivos JS transferidos por navegación disminuyera en unos 35%. Esa es la propuesta de valor fundamental aquí: no se trata de una función destinada a acelerar la primera carga, sino a optimizar el uso continuado de la aplicación durante diez minutos seguidos. La mayoría de las pruebas de tamaño del paquete que circulan en línea miden un escenario completamente diferente, uno que no refleja cómo se utilizan realmente las aplicaciones con muchas navegaciones internas.

El mismo equipo también revisó el valor de maxChunkCountPerGroup para un sitio de marketing que se encontraba en el mismo monorepo, reduciéndolo de 40 a 24. Ese sitio presenta principalmente sesiones de una sola página, por lo que las solicitudes de red adicionales generadas por un particionamiento más detallado constituían una carga innecesaria sin ningún beneficio. La misma herramienta subyacente, pero con ajustes opuestos; ambas opciones resultaron correctas para sus respectivos contextos. Esa es, sin duda, la verdadera lección: nunca existió una estrategia de particionamiento universalmente correcta; Turbopack simplemente se encargaba de tomar esa decisión por usted sin preguntar.

Comparación con los controles antiguos de Webpack

Para quienes han pasado años lidiando con splitChunks.cacheGroups, el análogo más cercano en este nuevo sistema es maxMergeChunkSize; a diferencia de muchos nombres utilizados por Webpack, este funciona exactamente como debería. La configuración de Webpack ofrecía una docena de parámetros poco documentados que interactuaban entre sí de maneras que nadie había comprendido del todo, y muchos equipos terminaron heredando archivos de configuración de ingenieros que ya no estaban, archivos que todos temían modificar. El enfoque de Turbopack es deliberadamente más limitado: se ofrecen dos o tres opciones en lugar de una docena, lo que sacrifica algo de flexibilidad ante casos extremadamente inusuales, pero también acorta la distancia entre darse cuenta de que “nuestro paquete no está bien” y saber exactamente qué número ajustar. Este compromiso es razonable de aceptar, especialmente para quienes prefieren no complicarse la vida.

No volveremos a tener una configuración de cacheGroups que dure varios años.

Hay otra distinción que merece ser mencionada por separado. Webpack obligaba en gran medida a pensar sobre el proceso de fragmentación durante la compilación, examinando detenidamente los resultados de stats.json e intentando predecir qué contendría realmente el caché del navegador de un visitante real. La fragmentación a nivel de componentes en Turbopack traslada parte de ese proceso al momento de la solicitud, donde el entorno en ejecución puede verificar el estado real del caché en lugar de hacer conjeturas por adelantado. Se trata de un cambio arquitectónico real, y no simplemente de un nombre diferente para una marca de configuración.

La desventaja

Esta función sigue etiquetada como experimental, y la documentación lo indica claramente. Un equipo se encontró con un caso en el que una biblioteca de gráficos importada dinámicamente se dividió de tal manera que provocó brevemente un error de “no se pudo cargar el fragmento” en producción durante una implementación, el conocido problema de referencia a fragmentos obsoletos que ha afectado prácticamente a todos los herramientas de empaquetado con soporte para división de código. Una actualización completa resolvió el problema para los usuarios afectados, pero sirve como recordatorio útil de que una división más detallada de los fragmentos amplía la superficie propensa a ese tipo de fallos en lugar de reducirla.

También está el problema de ajustar estas configuraciones sin datos reales de tráfico, lo cual es en esencia adivinar. Vale la pena resistir la tentación de modificar los valores predeterminados en un proyecto nuevo hasta contar con análisis reales de navegación que muestren si sus visitantes suelen irse después de una sola visita o son del tipo que recorren una docena o más de rutas en una misma sesión. Si esos datos aún no existen, este es un motivo tan bueno como cualquier otro para comenzar a recopilarlos antes de ajustar cualquier configuración de segmentación. Ajustar a ciegas conlleva un riesgo real de obtener una tasa de aciertos en el caché peor que la que habrían dado los valores predeterminados, y ese tipo de regresión ocurre silenciosamente. Nadie presenta un informe de error titulado “la navegación pareció un poco más lenta”; simplemente dejan de volver.

¿Vale la pena activarlo?

En el caso del panel de control, la respuesta fue sí y la configuración se mantuvo activa. En el sitio de marketing, solo se ajustó un número y eso fue todo. Nada de esto soluciona una aplicación fundamentalmente defectuosa, y si su paquete está sobrecargado debido a una dependencia que en realidad no necesitaba, ninguna estrategia de fragmentación del mundo podrá salvarlo de ese error. Lo que ofrece esta función es una solución específica para un problema concreto, lo cual representa un cambio refrescante en comparación con muchas de las funciones de herramientas de compilación lanzadas este año; un juicio que tiene cierto peso viniendo de alguien que ha pasado más noches de las deseables persiguiendo exactamente este tipo de errores.

Lecturas relacionadas