La Integration API: escribir un AstroIntegration
El otro lado del ecosistema: dejar de consumir integraciones y empezar a escribirlas. Qué es exactamente un AstroIntegration, por qué se reduce a un objeto con un nombre y un mapa de hooks, por qué casi todas se distribuyen como una función fábrica que devuelve ese objeto, cómo los hooks enganchan en momentos precisos del ciclo de vida del framework, y el criterio para decidir cuándo un problema pide una integración y cuándo basta con un módulo corriente.
La forma mínima: un nombre y un mapa de hooks
Una integración de Astro no es una clase ni un plugin con una API rebuscada: es un objeto plano con exactamente dos campos. El primero, name, es una cadena que la identifica en los mensajes y los diagnósticos. El segundo, hooks, es un mapa cuyas claves son nombres de momentos del ciclo de vida y cuyos valores son las funciones que quieres correr en cada uno. Nada más forma el contrato.
import type { AstroIntegration } from 'astro';const saludo: AstroIntegration = { name: 'saludo', hooks: { 'astro:config:setup': ({ logger }) => { logger.info('enganchada al ciclo de vida de Astro'); }, },};export default saludo;Ese objeto ya es una integración completa y válida: puedes enrolarlo en el array integrations de tu configuración y Astro llamará a su hook en el momento oportuno. El tipo AstroIntegration, importado de astro, no cambia el comportamiento —solo tipa el objeto para que el editor conozca los nombres de hook legales y la forma de las utilidades que cada uno recibe—. La lección de partida es esa desnudez: donde esperabas un framework de plugins, hay un objeto literal.
El patrón fábrica: por qué casi todas son funciones
Si el corazón es un objeto, ¿por qué las enrolas llamándolas, como en react() o sitemap(), con paréntesis? Porque una integración casi siempre admite opciones, y el idioma para parametrizar un objeto es envolver su construcción en una función. Esa función —la fábrica— recibe las opciones del usuario y devuelve el AstroIntegration ya configurado.
import type { AstroIntegration } from 'astro';interface Opciones { saludo?: string;}export default function saludador(opciones: Opciones = {}): AstroIntegration { const texto = opciones.saludo ?? 'hola'; return { name: 'saludador', hooks: { 'astro:config:setup': ({ logger }) => logger.info(texto), }, };}Conviene separar los dos planos con nitidez. El objeto es la integración; la función es solo la comodidad que la construye a partir de unas opciones. Cuando escribes saludador({ saludo: 'hey' }) en el array, no corres ningún hook: ejecutas la fábrica, que devuelve el objeto y lo deja enrolado. Los hooks no se disparan hasta que Astro alcanza cada fase de su ciclo. Registrar es una cosa; ejecutar, otra que llega después y la decide el framework.
Los hooks: enganches nombrados en el ciclo de vida
Cada clave de hooks es el nombre de un instante concreto del proceso de Astro, y la función asociada corre cuando el framework llega a ese instante —una sola vez, con unas utilidades hechas a medida de la fase—. No eliges cuándo corren tus funciones; eliges en qué momento del ciclo las cuelgas, y Astro las invoca al pasar por él.
El diagrama ordena los hooks que recorrerás en las próximas lecciones. astro:config:setup abre el ciclo: corre antes que nada, cuando la configuración aún se puede modificar. astro:config:done la sella. A partir de ahí el camino se bifurca según el comando: en dev se prepara el servidor, en build arranca la construcción, que pasa por la fase de servidor y termina cuando todo está en disco. Conocer ese orden es saber dónde puedes hacer cada cosa.
Cuándo escribir una integración
No todo problema pide una integración, y confundirlas con módulos corrientes lleva a sobre-ingeniería. La pregunta que decide es una sola: ¿necesito engancharme al ciclo de vida de Astro? Si tu código solo exporta un componente, una función de utilidad o un dato, no hay nada que enganchar —es un módulo que importas y ya está—. La integración se justifica cuando debes actuar sobre el framework mismo: modificar la configuración, inyectar rutas o scripts, añadir un middleware, tocar el build o el servidor de desarrollo.
// NO necesita integracion: es solo un modulo que se importaexport function formatearFecha(d: Date): string { return d.toISOString().slice(0, 10);}El segundo criterio es la reutilización. Una configuración que ajustas a mano en un proyecto no merece empaquetarse; pero si quieres que ese mismo comportamiento —un conjunto de rutas, un plugin de Vite, unos tipos generados— viaje intacto a varios proyectos o al ecosistema, la integración es el envase. Un adapter, que viste en el nivel de SSR, es exactamente esto: una integración con un rol reservado. La regla mental es limpia —si el problema vive dentro de tu app, escribe un módulo; si vive en el borde entre tu app y el framework, escribe una integración.
Queda un matiz que gobierna la convivencia: el orden en el array integrations importa. Cuando varias integraciones declaran el mismo hook, Astro las invoca en el orden en que aparecen, así que una que lee la configuración verá los parches de las que van antes y no los de las que van después. La mayoría son indiferentes al orden, pero las que dependen de lo que otra dejó —un adapter que reacciona a un renderer, por ejemplo— asumen esa secuencia. Escribir integraciones robustas es, en parte, no dar por sentado un orden que el usuario podría alterar.