wandres.dev
ESCRIBIR INTEGRACIONES · Integration API

El hook astro:config:setup

El hook más rico de la Integration API, el que abre el ciclo cuando la configuración todavía se puede cambiar. Qué recibe —config, command, isRestart, logger— y las utilidades que lo hacen poderoso: updateConfig para fundir cambios en la configuración sin pisarla, injectRoute para añadir rutas que no existen en src/pages, injectScript para colar código en cada página, addWatchFile para reaccionar a ficheros externos y addRenderer para enseñar a Astro un framework de UI.

⏱ 18 min

De todos los hooks, astro:config:setup es el primero en correr y el más generoso en poderes. Se dispara cuando Astro ha leído tu configuración pero aún no la ha sellado, en esa ventana única en la que la config todavía es materia blanda que se puede moldear. Por eso concentra las utilidades más transformadoras del framework: desde aquí una integración funde ajustes en la configuración, inventa rutas que no existen en src/pages, cuela scripts en cada página, vigila ficheros ajenos al proyecto y enseña a Astro a renderizar un framework nuevo. Dominar este hook es dominar la mayor parte de lo que una integración puede hacer.

🎯 Al terminar esta lección sabrás
  • Leer lo que astro:config:setup recibe: config, command, isRestart, logger.
  • Fundir cambios con updateConfig sin sobrescribir la configuración del usuario.
  • Añadir rutas y código con injectRoute e injectScript y sus etapas.
  • Reaccionar a ficheros con addWatchFile y registrar frameworks con addRenderer.

Lo que recibe el hook y updateConfig

El hook recibe un objeto con varias piezas. config es la configuración resuelta hasta ese punto, de solo lectura para ti. command te dice bajo qué orden corre Astro —dev, build, preview o sync—, y sirve para actuar distinto según el caso. isRestart es un booleano cierto cuando el servidor de desarrollo se reinicia en caliente. Y logger es tu canal de mensajes, atribuido a tu integración. Pero la pieza que transforma es updateConfig.

'astro:config:setup': ({ config, command, updateConfig, logger }) => {
  logger.info(`corriendo bajo el comando ${command}`);
  updateConfig({
    site: config.site ?? 'https://ejemplo.dev',
    vite: { ssr: { external: ['sharp'] } },
  });
},

La regla de oro de updateConfig es que funde, no sustituye. Lo que pasas se combina en profundidad con la configuración existente: añadir un plugin de Vite no borra los demás, fijar una clave no aniquila sus hermanas. Nunca debes mutar config a mano —es un retrato del estado, no la palanca—; el único camino legítimo para cambiar la configuración es updateConfig, que además devuelve la config ya fundida por si necesitas leer el resultado.

El command que recibes junto a la config no es adorno: es lo que te permite comportarte distinto según por qué te llamen. Una integración de depuración que inyecta una barra de herramientas solo tiene sentido bajo dev y estorbaría en build; una que genera un fichero de manifiesto solo importa cuando hay build de verdad. Ramificar sobre command —actuar en dev, callarte en build, o al revés— es la forma idiomática de que tu integración no haga en un contexto lo que solo pertenece a otro. Leer esa pista al principio del hook y decidir a partir de ella es una marca de integraciones bien educadas.

injectRoute: rutas que no viven en src/pages

Una integración puede añadir páginas y endpoints que el usuario nunca escribió, y lo hace con injectRoute. Le das un pattern —la URL bajo la que vivirá— y un entrypoint —el módulo, normalmente dentro de tu paquete, que la renderiza—. Astro la trata como una ruta más, indistinguible de las de src/pages.

'astro:config:setup': ({ injectRoute }) => {
  injectRoute({
    pattern: '/_estado',
    entrypoint: 'mi-integracion/paginas/estado.astro',
    prerender: false,
  });
},

Así es como una integración regala un panel de salud, una página de vista previa o un endpoint de webhook sin pedirle al usuario que copie ficheros. El pattern admite segmentos dinámicos igual que el enrutado normal; el entrypoint se resuelve como un import, así que suele ser una ruta de tu propio paquete. La opción prerender fija si esa ruta se hornea o se sirve bajo demanda, con independencia del resto del sitio.

injectScript: colar código en todas las páginas

Cuando tu integración necesita que cierto código corra en cada página —un script de analítica, un polyfill, una inicialización—, injectScript lo coloca sin que el usuario toque sus plantillas. Recibe dos argumentos: la etapa, que decide dónde y cómo se inyecta, y el código en sí.

🧩

head-inline

Un script en línea dentro del head de cada página. No se procesa ni se empaqueta: va tal cual lo escribes. Para etiquetas mínimas.

💧

before-hydration

Se importa antes de que se hidrate cualquier isla del cliente. El sitio para preparar el terreno del que dependen los componentes.

📄

page

Se trata como un script de un fichero Astro: se empaqueta y se incluye en cada página. Para código de cliente de verdad.

🖥️

page-ssr

Se importa en el frontmatter de cada página, en el servidor. Corre por página durante el render, no en el navegador.

'astro:config:setup': ({ injectScript }) => {
  injectScript('page-ssr', `import 'mi-integracion/registro.js';`);
},

Elegir la etapa es elegir el momento y el medio: head-inline para una etiqueta cruda en el head, before-hydration para lo que las islas necesitan antes de despertar, page para código de cliente empaquetado, page-ssr para lógica de servidor que corre en cada render. La etapa equivocada no falla ruidosamente; simplemente corre donde no querías.

addWatchFile y addRenderer

Dos utilidades más redondean el hook. addWatchFile le pide a Astro que vigile un fichero ajeno a src —un .env, un fichero de datos, la config de tu integración—, de modo que cuando ese fichero cambie, el servidor de desarrollo se reinicie y tu integración vuelva a correr con los datos frescos. Sin ella, un cambio en un fichero externo pasaría inadvertido.

'astro:config:setup': ({ addWatchFile }) => {
  addWatchFile(new URL('./datos.json', import.meta.url));
},

addRenderer, que ya viste al estudiar cómo se enrolan los frameworks de UI, registra un renderer con su serverEntrypoint y su clientEntrypoint. Es la utilidad que convierte a @astrojs/react o @astrojs/vue en lo que son. Rara vez la usarás salvo que escribas soporte para un framework nuevo, pero pertenece a este hook porque un renderer, como toda extensión, debe registrarse mientras la configuración aún es moldeable.

El hook guarda todavía un par de utilidades más especializadas que conviene conocer aunque las uses poco. addClientDirective registra una directiva de hidratación propia —una client: a tu medida, más allá de las que Astro trae— apuntando a un módulo que decide cuándo despertar la isla. Y addMiddleware, que verás en detalle en la próxima lección, inyecta un onRequest desde tu paquete. Todas comparten el mismo rasgo: solo existen aquí, en astro:config:setup, porque todas añaden algo al sistema y añadir solo es posible mientras la configuración sigue abierta.

flowchart TD
H[astro config setup] --> U[updateConfig funde ajustes]
H --> R[injectRoute anade rutas]
H --> S[injectScript cuela codigo]
H --> W[addWatchFile vigila ficheros]
H --> D[addRenderer registra frameworks]
U --> DONE[configuracion sellada en config done]
style H fill:#89b4fa,color:#11111b
style DONE fill:#a6e3a1,color:#11111b
ℹ️
updateConfig funde en profundidad y devuelve el resultado

La diferencia entre updateConfig y mutar config a mano no es de estilo, es de corrección. updateConfig combina tu parche con la configuración existente respetando lo que otras integraciones y el usuario ya pusieron; mutar config lo saltaría todo y produciría estados incoherentes. Además, updateConfig devuelve la configuración ya fundida, así que si necesitas leer el valor final de una clave que acabas de tocar, léelo de lo que devuelve, no de la config original.

💡
Respeta lo que el usuario ya configuró

Antes de fijar una clave, mira si el usuario ya la puso. El patrón idiomático es leer de config y usar tu valor solo como defecto —config.site ?? miDefecto— en lugar de imponerlo. Una integración educada rellena huecos, no sobreescribe decisiones; la que pisa la configuración del usuario genera sorpresas difíciles de depurar, porque el ajuste que él escribió deja de tener efecto sin razón aparente.

⚠️
El entrypoint de injectRoute se resuelve como un import

El entrypoint que pasas a injectRoute no es una ruta de sistema de ficheros cualquiera: se resuelve como un especificador de módulo. Por eso apunta a algo importable —una ruta dentro de tu paquete publicado, como mi-integracion/paginas/estado.astro—, no a una ruta absoluta de tu disco. Si le das una ruta local que solo existe en tu máquina, la integración funcionará para ti y se romperá para quien la instale.

📝
El código de injectScript viaja como una cadena

Lo que pasas a injectScript no es una función ni una referencia a un módulo: es una cadena de texto con código, que Astro procesa según la etapa. Por eso lo habitual es que esa cadena se limite a un import de un fichero de tu paquete —import 'mi-integracion/registro.js'— en lugar de escribir la lógica en línea. Así el código de verdad vive en un fichero normal, con tipos y sin escapar comillas, y la cadena inyectada solo actúa de disparador que lo trae a cada página.

config:setup es poderoso porque la configuración aún no ha cuajado

La potencia desmedida de este hook no es un capricho de la API: es la consecuencia directa de cuándo corre. Astro divide el nacimiento de un sitio en un antes y un después nítidos, y la frontera tiene nombre —astro:config:done—. Todo lo que ocurre antes de esa frontera sucede sobre una configuración que todavía es líquida: se puede verter en ella, fundir claves, añadir rutas, registrar renderers, porque nada de lo que dependerá de ella se ha construido aún. Todo lo que ocurre después sucede sobre una configuración solidificada, un hecho consumado que ya no admite cambios porque el resto del sistema ya se apoyó en él. astro:config:setup es el único hook que vive del lado líquido, y por eso acumula las utilidades que transforman en lugar de las que solo observan. Aquí hay un principio de diseño que reaparece en todo sistema serio con fases: la mutabilidad no es un valor absoluto, es una ventana. Hay un instante en el que cambiar algo es barato y seguro porque nadie ha leído aún ese algo, y un instante posterior en el que el mismo cambio sería catastrófico porque medio sistema ya se construyó suponiendo lo contrario. El arte de una API por fases consiste en abrir la ventana de mutación de par en par mientras es segura y clausurarla en seco en cuanto deja de serlo, y en decirte con claridad de qué lado de la frontera estás. Cuando entiendes que updateConfig solo existe en config:setup porque es el último momento en que la configuración todavía no le debe nada a nadie, dejas de preguntarte por qué no puedes cambiar la config más tarde y empiezas a diseñar tus propias integraciones alrededor de esa verdad: lo que transforma, temprano; lo que observa, cuando quieras.

⚔️ Ejerce los poderes de config:setup
  1. Escribe una integración que en astro:config:setup lea config.site y, solo si está vacío, lo rellene con updateConfig; comprueba que respeta el valor cuando el usuario ya lo puso.
  2. Inyecta una ruta con injectRoute que apunte a una página de tu integración y visítala en el navegador aunque no exista en src/pages.
  3. Cuela un injectScript('head-inline', ...) con un comentario reconocible y localízalo en el head del HTML servido.
  4. Registra un addWatchFile sobre un .json de datos, edítalo con el servidor arrancado y confirma que el dev se reinicia solo.