sequence(): encadenar varios middlewares
Cuando un solo onRequest acumula demasiadas responsabilidades, se parte en piezas pequeñas y enfocadas que sequence combina en uno solo. Cómo se importa sequence de astro:middleware, cómo cada eslabón recibe context y next, por qué el orden de la petición sigue el modelo de la cebolla —ida en el orden declarado, vuelta al revés— y cómo los locals que escribe uno los leen los siguientes.
Un onRequest que lo hace todo se vuelve inmanejable
Nada te impide meter toda la lógica transversal en una sola función. Al principio parece cómodo: un archivo, un onRequest, todo a la vista. Pero cada asunto que añades —autenticación, idioma, registro, seguridad— tiene su propia condición, sus propias ramas y su propio momento, y mezclarlos en un cuerpo único los enreda hasta que cambiar uno arriesga romper otro. La señal de alarma es clásica: una función que hace y esto y aquello y lo de más allá está pidiendo a gritos que la separes.
La cura es tratar cada preocupación como un middleware independiente, en su propio archivo, con su nombre y su único cometido. Uno se ocupa de la sesión y nada más; otro, del idioma y nada más; otro, del registro. Cada uno es pequeño, legible y comprobable por separado. Lo que antes era un párrafo confuso se vuelve un puñado de funciones enfocadas, y el archivo src/middleware.ts queda para una sola cosa: decir en qué orden se aplican.
sequence: varios middlewares en fila
La función sequence, importada de astro:middleware, toma varios middlewares y devuelve uno solo que los ejecuta en fila. Ese resultado es lo que exportas como onRequest. Cada eslabón se escribe como el middleware de siempre —recibe context y next, devuelve una Response—; lo que cambia es que su next ya no dispara directamente el renderizado, sino que cede el turno al siguiente middleware de la secuencia.
// src/middleware.tsimport { sequence } from 'astro:middleware';import { logging } from './middleware/logging';import { auth } from './middleware/auth';import { i18n } from './middleware/i18n';export const onRequest = sequence(logging, auth, i18n);Cada pieza vive en su archivo y se ocupa de lo suyo. Fíjate en que ninguna sabe de las otras: auth no conoce a i18n, solo llama a next para ceder el paso a quienquiera que venga después. Esa ignorancia mutua es la que permite reordenarlas, quitar una o añadir otra sin tocar su código, cambiando únicamente la línea de sequence.
// src/middleware/auth.tsimport { defineMiddleware } from 'astro:middleware';export const auth = defineMiddleware(async (context, next) => { const sid = context.cookies.get('sid')?.value; context.locals.usuario = await buscarSesion(sid); return next();});El context es el mismo objeto para toda la secuencia, así que es también el canal por el que los eslabones se hablan. Si auth deposita el usuario en context.locals, cualquier middleware posterior —y la página al final— lo encontrará ya resuelto. Por eso el orden no es solo estético: un eslabón que depende de un dato debe ir después del que lo produce.
Mira cómo un segundo eslabón aprovecha lo que dejó el primero. Este i18n lee el usuario que auth ya resolvió para elegir su idioma preferido, y publica el resultado en locals para las páginas:
// src/middleware/i18n.tsimport { defineMiddleware } from 'astro:middleware';export const i18n = defineMiddleware((context, next) => { const preferido = context.locals.usuario?.idioma; context.locals.idioma = preferido ?? 'es'; return next();});Que esto funcione depende por completo del orden: si i18n corriera antes que auth, leería un context.locals.usuario todavía vacío y elegiría siempre el idioma por defecto. La secuencia no es una lista cualquiera; es una cadena de dependencias donde cada eslabón presupone lo que los anteriores ya dejaron hecho.
El orden importa: la cebolla de la petición
Aquí está la parte que más confunde y que hay que grabar. Cuando sequence encadena tres middlewares, la petición los recorre como las capas de una cebolla: entra atravesándolas en el orden declarado, llega al centro donde se renderiza la ruta, y sale atravesándolas de nuevo pero en orden inverso. El código que pusiste antes de next corre a la ida, en el orden de la secuencia; el que pusiste después de next corre a la vuelta, del último al primero.
// src/middleware/logging.tsimport { defineMiddleware } from 'astro:middleware';export const logging = defineMiddleware(async (context, next) => { const inicio = Date.now(); // ida: primero de todos const response = await next(); // deja correr al resto y al render const ms = Date.now() - inicio; // vuelta: ultimo de todos console.log(context.url.pathname, `${ms}ms`); return response;});Colocado como primer eslabón, logging es el primero en empezar y el último en terminar: su await next() no vuelve hasta que auth, i18n y el renderizado entero han pasado. Por eso envuelve a todos los demás, y por eso mide el tiempo real de la petición completa. Esa es la lógica de la cebolla: el primero de la fila es el que más afuera está, el que abraza a todo lo que viene detrás. Entenderlo te deja decidir con criterio dónde poner cada pieza: lo que debe vigilar todo va primero; lo que depende de un dato va después de quien lo produce.
Reordenar es rediseñar: cómo elegir el orden
Si el orden de una sequence es su arquitectura, conviene tener heurísticas para fijarlo con criterio, y tres bastan para casi todos los casos. La primera: lo que envuelve, va primero. Un middleware que mide o registra la petición entera —como logging— debe ir al principio, porque solo desde la capa más externa abraza todo lo que ocurre dentro, el render incluido.
La segunda: quien produce, antes que quien consume. Si i18n necesita el usuario para elegir idioma, auth —que lo resuelve y lo deja en locals— tiene que ir antes. Las dependencias de datos entre eslabones dictan su orden con la misma fuerza con la que las dependencias entre módulos dictan el suyo.
La tercera: quien puede vetar, cuanto antes. Un guardián que corta la cadena ahorra todo el trabajo posterior si actúa pronto; ponerlo tarde significa preparar datos que un veto tirará a la basura. Con estas tres reglas —envolver fuera, producir antes de consumir, vetar temprano— el orden deja de ser una corazonada y pasa a ser una consecuencia de las relaciones reales entre tus piezas.