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.
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 fallaLo 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.
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.
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 servidorexport 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.
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.
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
windowodocumenten el servidor pide aislar el código a una isla de cliente, no un ajuste del bundler.