wandres.dev
VITE 8 Y ROLLDOWN · el motor por debajo

Diagnóstico: verbose, analizar el bundle y afinar SSR

Cómo diagnosticar y afinar un build de Astro cuando algo falla o pesa de más: encender los logs detallados con --verbose, analizar el bundle para ver qué ocupa y por qué, reconocer los problemas más comunes de SSR y de dependencias en CommonJS —accesos a globales del navegador, interoperabilidad de módulos, externalización errónea— y aplicar el bucle de diagnóstico que convierte un fallo opaco en un tornillo concreto de la clave vite.

⏱ 17 min

Tarde o temprano un build se rompe o engorda sin explicación aparente, y ahí se separa a quien sabe leer la máquina de quien solo la ejecuta. Diagnosticar no es adivinar: es un método. Encender los logs adecuados, aislar la pieza culpable, leer el mensaje de error de verdad en vez de la primera línea, y girar el tornillo correcto de la configuración de Vite. Esta lección te da ese método y el catálogo de los fallos más frecuentes —casi todos concentrados en la frontera del SSR y las dependencias en CommonJS— para que dejes de temer al build y empieces a interrogarlo.

🎯 Al terminar esta lección sabrás
  • Encender los logs detallados del build con --verbose y saber qué buscar en ellos.
  • Analizar el bundle para descubrir qué módulos pesan y quién los arrastra.
  • Reconocer los problemas comunes de SSR y de dependencias en CommonJS por su síntoma.
  • Aplicar un bucle de diagnóstico que traduzca cada fallo en un ajuste concreto de la clave vite.

Encender las luces: –verbose

Por defecto Astro te muestra un resumen limpio del build. Cuando algo va mal, ese resumen es demasiado educado: oculta justo los detalles que necesitas. La bandera --verbose levanta la tapa y saca los logs internos de Vite y de cada fase, incluida la actividad de los plugins y los módulos que se transforman. Su opuesto, --silent, calla todo salvo los errores, útil en integración continua.

Con --verbose activo, el log deja ver cosas que el resumen esconde:

  • Qué plugin procesa cada módulo y cuánto tarda, para localizar un cuello de botella en la transformación.
  • Qué dependencias se pre-empaquetan al arrancar y cuáles se re-optimizan a mitad de sesión.
  • El detalle de un error: la cadena de imports que trajo el módulo culpable, no solo el síntoma final.
astro build --verbose
# imprime los logs detallados de vite y de cada fase del build
# ahi se ve que plugin, que modulo o que dependencia tarda o falla

Lo primero que hay que aprender a leer no es el final del log, sino el resumen de rutas que Astro imprime al construir: qué páginas se prerenderizaron, cuáles quedaron para render bajo demanda y cuánto tardó cada fase. Ese resumen te orienta antes de bucear en el detalle. Y cuando Vite avisa de que un chunk supera cierto tamaño, no lo ignores: es la primera pista de un problema de peso que conviene rastrear hasta su origen.

📝
Los niveles de log tienen un propósito, no son ruido

Astro y Vite gradúan sus mensajes por severidad: error, aviso, información y depuración. En el día a día trabajas con el nivel por defecto, que calla el detalle. Cuando diagnosticas, --verbose baja el umbral y saca la capa de depuración; cuando automatizas en integración continua, --silent lo sube y deja solo los errores que deben romper la tubería. Elegir el nivel adecuado es parte del método: demasiado ruido esconde la señal tanto como demasiado silencio.

💡
Lee el mensaje entero, no la primera línea

La mayoría de los errores de build se abandonan en la primera línea, que suele ser genérica. La causa real casi siempre está más abajo: el nombre del módulo que falló, la ruta del import que lo trajo, la fase concreta que lo procesaba. Antes de buscar el mensaje en internet, léelo completo hasta el final. Nueve de cada diez veces, el propio error nombra la dependencia culpable y hasta insinúa la cura.

Analizar el bundle: qué pesa y por qué

Cuando el problema no es un fallo sino un peso, necesitas ver el interior del bundle. La técnica estándar es enchufar un visualizador a través de vite.plugins: un plugin de la API de Rollup, compatible con Rolldown, que genera un mapa de tamaños donde cada módulo ocupa un área proporcional a su peso. De un vistazo ves qué dependencia domina y por dónde entró.

import { defineConfig } from 'astro/config';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  vite: {
    plugins: [visualizer({ open: true, gzipSize: true })],
  },
});

Con el mapa delante, la pregunta útil no es cuánto pesa una librería, sino quién la importa y si ese import justifica su presencia en el cliente. A menudo descubres que una dependencia pesada llegó al bundle del navegador por un import colocado en el sitio equivocado, cuando debía quedarse en el servidor o diferirse. Analizar el bundle es, sobre todo, rastrear responsabilidades por el grafo hacia arriba.

Con el mapa a la vista, tres preguntas ordenan la investigación y evitan optimizar a ciegas:

  • ¿Esta dependencia debería estar en el cliente, o llegó por un import que podría vivir solo en el servidor?
  • ¿Se puede diferir con un import() dinámico para que no cargue en el arranque de la página?
  • ¿Existe una alternativa más ligera, o una forma de importar solo la parte que de verdad se usa?

Problemas comunes de SSR y dependencias CJS

La inmensa mayoría de los fallos de build en un sitio con SSR se concentran en un puñado de patrones reconocibles. Aprender su síntoma es aprender su cura.

🪟

Acceso a globales del navegador

Una librería toca window o document en su nivel superior. En el servidor esos objetos no existen y el render revienta. La cura suele ser cargarla solo en cliente o diferir su uso.

🔀

Interoperabilidad ESM y CJS

Un import por defecto de un paquete CommonJS que no expone un default claro. El error habla de una exportación que no existe. Empaquetarla con ssr.noExternal deja que el bundler resuelva la interoperabilidad.

📤

Externalización errónea

Una dependencia que debía empaquetarse se externalizó y el runtime no la encuentra al arrancar. Moverla a ssr.noExternal la mete en el bundle del servidor.

🧱

Built-ins de Node en el edge

Código que usa módulos nativos de Node desplegado en un runtime que no los tiene. La cura es sustituir la dependencia o elegir un adapter y runtime compatibles.

El caso más frecuente de todos es el de la interoperabilidad. Un paquete publicado solo en CommonJS asigna su interfaz de forma dinámica, y cuando lo importas con la sintaxis ESM el bundler no encuentra la exportación que esperas, porque no puede analizar estáticamente qué expone. La primera cura a probar es siempre la misma: llevar ese paquete a ssr.noExternal para que se empaquete y transforme dentro del servidor en lugar de importarse crudo. Si el problema es el opuesto —un binario nativo que el bundler no debe tocar— la cura es ssr.external.

En la práctica, esa primera cura cabe en tres líneas de configuración:

// astro.config.mjs: empaquetar un paquete CJS problematico en el servidor
export default defineConfig({
  vite: { ssr: { noExternal: ['paquete-cjs-que-rompe-en-ssr'] } },
});

Si tras el cambio el error persiste pero ahora menciona un binario nativo o un módulo del sistema, es señal de que el paquete debía ir en el lado contrario, ssr.external, para que el bundler ni lo toque y lo deje al runtime.

⚠️
window y document no existen en el servidor

El fallo de SSR más desconcertante para quien viene del cliente es el de una librería que asume que hay un navegador. Si el error menciona window, document o self como no definidos durante el build o el render de servidor, la causa es una dependencia que accede a globales del navegador al importarse, no al usarse. No lo arregles con noExternal: eso solo cambia dónde se empaqueta, no elimina el acceso. La solución real es aislar ese código a una isla de cliente o cargarlo perezosamente, de modo que solo se ejecute donde esos globales existen.

Afinar: el bucle de diagnóstico

Todo lo anterior se ordena en un bucle repetible. No se trata de probar tornillos al azar, sino de recorrer siempre las mismas cuatro estaciones hasta que el fallo cede.

flowchart LR
REPRO[reproducir el fallo] --> AISLAR[aislar la dependencia o pagina]
AISLAR --> LEER[leer el mensaje real del error]
LEER --> AJUSTAR[girar un tornillo de la clave vite]
AJUSTAR --> MEDIR[reconstruir y medir]
MEDIR --> REPRO
style LEER fill:#89b4fa,color:#11111b
style AJUSTAR fill:#a6e3a1,color:#11111b

Reproducir con fidelidad significa construir de verdad, no confiar en que el dev server basta: muchos fallos de SSR solo aparecen en el build porque el desarrollo no empaqueta el servidor igual. Aislar significa reducir el proyecto hasta que quede solo la ruta o la dependencia que falla. Leer significa agotar el mensaje completo. Ajustar significa aplicar el tornillo que el diagnóstico señala —noExternal, external, optimizeDeps, un alias— y no tres a la vez. Y medir cierra el ciclo: reconstruyes y confirmas que el cambio resolvió lo que debía sin romper otra cosa.

Con el tiempo el paso de ajustar se vuelve casi mecánico, porque los síntomas se repiten. Vale la pena memorizar el mapa de cada uno a su tornillo:

  • Un módulo que no se encuentra al arrancar el servidor suele pedir ssr.noExternal.
  • Un binario nativo que el bundler no debe procesar pide ssr.external.
  • Una dependencia con imports CJS ocultos que el dev no detecta pide optimizeDeps.include.
  • Un acceso a window o document en el servidor pide aislar el código a una isla de cliente, no un ajuste del bundler.
Diagnosticar es negarse a tratar la herramienta como magia

Hay una diferencia de fondo entre quien usa una herramienta y quien la comprende, y no está en la cantidad de comandos que memoriza, sino en su reacción ante lo inesperado. Cuando un build falla, la respuesta inmadura es tratar el error como un oráculo hostil: copiar el mensaje, pegarlo en un buscador, aplicar la primera solución ajena que aparezca y rezar. A veces funciona, y esa es justamente la trampa, porque refuerza el hábito de no entender. La respuesta madura es la contraria: asumir que el build es un sistema determinista que hizo exactamente lo que su entrada le dictó, y que un error no es un castigo sino información —la máquina describiendo, con la precisión de la que es capaz, la contradicción que encontró—. Desde esa postura, el mensaje de error deja de ser un muro y se convierte en un mapa. Que una librería falle porque toca window en el servidor no es un misterio: es la consecuencia lógica de ejecutar código de navegador donde no hay navegador, y el arreglo se deduce del propio diagnóstico en lugar de copiarse de un desconocido. Que un import por defecto de un paquete CommonJS no encuentre su exportación no es mala suerte: es lo que ocurre cuando pides análisis estático a un módulo que solo se conoce ejecutándolo, y noExternal no es un conjuro, sino la instrucción precisa de empaquetarlo para que la interoperabilidad se resuelva. El método —reproducir, aislar, leer, ajustar, medir— no es burocracia: es la disciplina de mantener una sola variable en el aire cada vez, para que la relación entre causa y efecto quede visible en lugar de enterrada bajo tres cambios simultáneos que no sabes cuál funcionó. Interiorizar esto transforma tu relación con toda la pila, no solo con Astro: dejas de temer los builds grandes, dejas de acumular configuración cargo-cult que copiaste sin entender, y empiezas a tratar cada fallo como lo que es, una oportunidad de que el sistema te enseñe cómo funciona por dentro. La herramienta que parecía caprichosa se revela, bajo el método, perfectamente legible. No había magia: había un grafo, unas reglas y un mensaje que decía la verdad, esperando a que alguien lo leyera entero.

⚔️ Conviértete en el depurador del build
  1. Ejecuta astro build --verbose en tu proyecto y localiza en el log detallado la fase más lenta y los avisos de tamaño de chunk que antes no veías.
  2. Enchufa un visualizador de bundle por vite.plugins, genera el mapa y nombra las tres dependencias que más pesan y quién las importa.
  3. Provoca a propósito un fallo de SSR importando en una ruta de servidor un paquete CommonJS problemático, lee el error completo y resuélvelo con el tornillo correcto de la clave vite.
  4. Documenta tu propio bucle de diagnóstico para ese fallo: qué reprodujiste, cómo lo aislaste, qué decía el mensaje, qué ajustaste y cómo mediste que quedó resuelto.