Middleware
Middleware permite interceptar solicitudes y respuestas e inyectar comportamientos dinámicamente cada vez que una página o endpoint está a punto de ser renderizado. Este renderizado ocurre en tiempo de build para todas las páginas pre-renderizadas, pero ocurre cuando la ruta es solicitada para las páginas renderizadas bajo demanda, haciendo que características SSR adicionales como cookies y headers estén disponibles.
Middleware también permite establecer y compartir información específica de la solicitud entre endpoints y páginas mutando un objeto locals que está disponible en todos los componentes de Astro y endpoints de API. Este objeto está disponible incluso cuando el middleware se ejecuta en tiempo de build.
Uso básico
Sección titulada “Uso básico”-
Crea
src/middleware.js|ts(Alternativamente, puedes crearsrc/middleware/index.js|ts.) -
Dentro de este archivo, exporta una función
onRequest()que puede recibir unobjeto contexty una funciónnext(). Esta no debe ser una exportación por defecto.src/middleware.js export function onRequest (context, next) {// intercept data from a request// optionally, modify the properties in `locals`context.locals.title = "New title";context.locals.property = "information";// return a Response or the result of calling `next()`return next();}; -
Dentro de cualquier archivo
.astro, accede a los datos de respuesta usandoAstro.locals.src/components/Component.astro ---const data = Astro.locals;---<h1>{data.title}</h1><p>This {data.property} is from middleware.</p>
El objeto context
Sección titulada “El objeto context”El objeto context incluye información para hacer disponible a otros middleware, rutas de API y rutas .astro durante el proceso de renderizado.
Este es un argumento opcional pasado a onRequest() que puede contener el objeto locals así como cualquier propiedad adicional para ser compartida durante el renderizado. Por ejemplo, el objeto context puede incluir cookies usadas en la autenticación.
Almacenar datos en context.locals
Sección titulada “Almacenar datos en context.locals”context.locals es un objeto que puede ser manipulado dentro del middleware.
Este objeto locals se reenvía a través del proceso de manejo de solicitudes y está disponible como propiedad de APIContext y AstroGlobal. Esto permite que los datos se compartan entre middlewares, rutas de API, y páginas .astro. Esto es útil para almacenar datos específicos de la solicitud, como datos de usuario, a través del paso de renderizado.
Las Integraciones pueden establecer propiedades y proporcionar funcionalidad a través del objeto locals. Si estás usando una integración, consulta su documentación para asegurarte de que no estás sobrescribiendo ninguna de sus propiedades ni haciendo trabajo innecesario.
Puedes almacenar cualquier tipo de dato dentro de locals: strings, números, e incluso tipos de datos complejos como funciones y mapas.
export function onRequest (context, next) { // intercept data from a request // optionally, modify the properties in `locals` context.locals.user = { id: 1, name: "John Wick" }; context.locals.welcomeTitle = () => { return "Welcome back " + context.locals.user.name; }; context.locals.orders = new Map([["1", { product: "socks" }]]); context.locals.property = "information";
// return a Response or the result of calling `next()` return next();};Luego puedes usar esta información dentro de cualquier archivo .astro con Astro.locals.
---const title = Astro.locals.welcomeTitle();const orders = Array.from(Astro.locals.orders.entries());const data = Astro.locals;---<h1>{title}</h1><p>This {data.property} is from middleware.</p><ul> {orders.map(order => { return <li>{/* do something with each order */}</li>; })}</ul>locals es un objeto que vive y muere dentro de una sola ruta de Astro; cuando la página de tu ruta es renderizada, locals ya no existirá y se creará uno nuevo. La información que necesita persistir a través de múltiples solicitudes de página debe almacenarse en otro lugar.
El valor de locals no puede ser sobrescrito en tiempo de ejecución. Hacerlo arriesgaría borrar toda la información almacenada por el usuario. Astro realiza verificaciones y lanzará un error si locals se sobrescribe.
Ejemplo: redactar información sensible
Sección titulada “Ejemplo: redactar información sensible”El ejemplo siguiente usa middleware para reemplazar “PRIVATE INFO” con la palabra “REDACTED” para permitirte renderizar HTML modificado en tu página:
export const onRequest = async (context, next) => { const response = await next(); const html = await response.text(); const redactedHtml = html.replaceAll("PRIVATE INFO", "REDACTED");
return new Response(redactedHtml, { status: 200, headers: response.headers });};Tipos de middleware
Sección titulada “Tipos de middleware”Puedes importar y usar la función utilitaria defineMiddleware() para aprovechar la seguridad de tipos:
import { defineMiddleware } from "astro:middleware";
// `context` and `next` are automatically typedexport const onRequest = defineMiddleware((context, next) => {
});En su lugar, si estás usando JsDoc para aprovechar la seguridad de tipos, puedes usar MiddlewareHandler:
/** * @type {import("astro").MiddlewareHandler} */// `context` and `next` are automatically typedexport const onRequest = (context, next) => {
};Para tipar la información dentro de Astro.locals, lo que te da autocompletado dentro de los archivos .astro y el código del middleware, extiende los tipos globales declarando un namespace global en el archivo env.d.ts:
type User = { id: number; name: string;};
declare namespace App { interface Locals { user: User; welcomeTitle: () => string; orders: Map<string, object>; session: import("./lib/server/session").Session | null; }}Luego, dentro del archivo de middleware, puedes aprovechar el autocompletado y la seguridad de tipos.
Encadenar middleware
Sección titulada “Encadenar middleware”Múltiples middlewares se pueden unir en un orden especificado usando sequence():
import { sequence } from "astro:middleware";
async function validation(_, next) { console.log("validation request"); const response = await next(); console.log("validation response"); return response;}
async function auth(_, next) { console.log("auth request"); const response = await next(); console.log("auth response"); return response;}
async function greeting(_, next) { console.log("greeting request"); const response = await next(); console.log("greeting response"); return response;}
export const onRequest = sequence(validation, auth, greeting);Esto resultará en el siguiente orden en la consola:
validation requestauth requestgreeting requestgreeting responseauth responsevalidation responseReescritura
Sección titulada “Reescritura”Agregado en:
astro@4.13.0
El APIContext expone un método llamado rewrite() que funciona de la misma manera que Astro.rewrite.
Usa context.rewrite() dentro del middleware para mostrar el contenido de una página diferente sin redirigir a tu visitante a una nueva página. Esto disparará una nueva fase de renderizado, causando que cualquier middleware se vuelva a ejecutar.
import { isLoggedIn } from "~/auth.js"export function onRequest (context, next) { if (!isLoggedIn(context)) { // If the user is not logged in, update the Request to render the `/login` route and // add header to indicate where the user should be sent after a successful login. // Re-execute middleware. return context.rewrite(new Request("/login", { headers: { "x-redirect-to": context.url.pathname } })); }
return next();};También puedes pasar a la función next() un parámetro opcional de ruta URL para reescribir el Request actual sin redisparar una nueva fase de renderizado. La ubicación de la ruta de reescritura se puede proporcionar como un string, URL, o Request:
import { isLoggedIn } from "~/auth.js"export function onRequest (context, next) { if (!isLoggedIn(context)) { // If the user is not logged in, update the Request to render the `/login` route and // add header to indicate where the user should be sent after a successful login. // Return a new `context` to any following middlewares. return next(new Request("/login", { headers: { "x-redirect-to": context.url.pathname } })); }
return next();};La función next() acepta la misma carga útil de la función Astro.rewrite(). La ubicación de la ruta de reescritura se puede proporcionar como un string, URL, o Request.
Cuando tienes múltiples funciones de middleware encadenadas vía sequence(), pasar una ruta a next() reescribirá el Request en su lugar y el middleware no se ejecutará nuevamente. La siguiente función de middleware en la cadena recibirá el nuevo Request con su context actualizado.
Llamar a next() con esta firma creará un nuevo objeto Request usando el antiguo ctx.request. Esto significa que intentar consumir Request.body, ya sea antes o después de esta reescritura, lanzará un error en tiempo de ejecución. Este error a menudo se produce con Astro Actions que usan formularios HTML. En estos casos, recomendamos manejar las reescrituras desde tus plantillas de Astro usando Astro.rewrite() en lugar de usar middleware.
// Current URL is https://example.com/blog
// First middleware functionasync function first(context, next) { console.log(context.url.pathname) // this will log "/blog" // Rewrite to a new route, the homepage // Return updated `context` which is passed to next function return next("/")}
// Current URL is still https://example.com/blog
// Second middleware functionasync function second(context, next) { // Receives updated `context` console.log(context.url.pathname) // this will log "/" return next()}
export const onRequest = sequence(first, second);Páginas de error
Sección titulada “Páginas de error”El middleware intentará ejecutarse para todas las páginas renderizadas bajo demanda, incluso cuando no se pueda encontrar una ruta coincidente. Esto incluye la página 404 por defecto (en blanco) de Astro y cualquier página 404 personalizada. Sin embargo, depende del adaptador decidir si ese código se ejecuta. Algunos adaptadores pueden servir una página de error específica de la plataforma en su lugar.
El middleware también intentará ejecutarse antes de servir una página de error 500, incluyendo una página 500 personalizada, a menos que el error del servidor haya ocurrido en la ejecución del middleware mismo. Si tu middleware no se ejecuta exitosamente, entonces no tendrás acceso a Astro.locals para renderizar tu página 500.