astro-middleware

Cómo usar Middleware en Astro

  • 5 min

El Middleware es un código que se ejecuta antes de que Astro procese cualquier página o endpoint. Nos permite interceptar la petición, modificarla o bloquearla completamente.

Si has trabajado con frameworks de backend como Express o Koa, el concepto te resultará familiar.

A diferencia de otros frameworks donde el middleware solo existe si tienes un servidor Node.js corriendo, en Astro el middleware está disponible en ambos modos, aunque su momento de ejecución cambia:

  • En modo SSR (output: ‘server’): El middleware se ejecuta en tiempo de solicitud. Cada vez que un usuario visita una página, el middleware se activa. Es ideal para autenticación, lectura de cookies o redirecciones dinámicas.

  • En modo SSG (output: ‘static’): El middleware se ejecuta en tiempo de compilación (Build time). Se activa cuando haces npm run build mientras Astro genera las páginas HTML. Es útil para manipular el HTML final, inyectar datos en la compilación o generar logs de construcción.

En este artículo nos centraremos principalmente en los casos de uso dinámicos (SSR), que es donde el middleware tiene más sentido (protección de rutas, usuarios), pero ten en cuenta que la arquitectura es la misma para ambos

Creando el Middleware

En Astro, el middleware se define en un único archivo especial: src/middleware.ts (o .js). No puedes ponerle otro nombre ni moverlo de carpeta.

La estructura básica es la siguiente:

// src/middleware.ts
import { defineMiddleware } from "astro:middleware";

// La función 'onRequest' intercepta TODAS las peticiones
export const onRequest = defineMiddleware(async (context, next) => {
  console.log("Interceptada petición a:", context.url.pathname);

  // 'next()' pasa la pelota al siguiente paso (renderizar la página)
  // Debemos devolver su respuesta
  const response = await next();

  console.log("Página renderizada. Devolviendo respuesta...");
  return response;
});
Copied!

Este código actúa como un “sandwich”. Hacemos cosas antes de next() (pre-procesamiento) y cosas después de next() (post-procesamiento).

Encadenando Middlewares (sequence)

En aplicaciones grandes, tener todo el código en una sola función onRequest puede ser un caos. Podrías tener lógica de autenticación, lógica de traducción (i18n), lógica de analítica, etc.

Astro nos permite dividir el middleware en funciones pequeñas y encadenarlas usando sequence.

// src/middleware.ts
import { sequence } from "astro:middleware";

async function auth(context, next) {
  console.log("1. Comprobando Auth");
  // Lógica de auth...
  return next();
}

async function validation(context, next) {
  console.log("2. Validando datos");
  // Lógica de validación...
  return next();
}

async function logging(context, next) {
  console.log("3. Registrando visita");
  return next();
}

// Exportamos la secuencia en orden
export const onRequest = sequence(auth, validation, logging);
Copied!

El orden importa: auth se ejecutará primero. Si auth decide redirigir y no llamar a next(), validation y logging nunca se ejecutarán.

Manipulación de la respuesta

A veces queremos modificar la respuesta HTML después de que la página se haya renderizado. Por ejemplo, para añadir cabeceras de seguridad o minificar el HTML.

export const onRequest = defineMiddleware(async (context, next) => {
  const response = await next(); // Esperamos a que se genere el HTML

  // Modificamos headers
  response.headers.set("X-Powered-By", "Astro + LuisLlamas");

  // Incluso podemos modificar el HTML (cuidado con el rendimiento)
  const html = await response.text();
  const newHtml = html.replace("</body>", "</body>");

  return new Response(newHtml, {
    status: 200,
    headers: response.headers
  });
});
Copied!

Interceptar y reescribir el cuerpo HTML (response.text()) tiene un coste de rendimiento alto. Úsalo solo si es estrictamente necesario.