defineRouteMiddleware
Declara un middleware de ruta de app/middleware, global o con nombre, que se ejecuta antes de la página en SSR y en cada navegación del navegador.
Se autoimporta en las aplicaciones del framework.
import { defineRouteMiddleware } from "@meekojs/meeko/framework";defineRouteMiddleware declara el middleware de ruta que exporta por defecto un archivo de app/middleware/. Se ejecuta antes de las opciones de ruta y del loader de la página: en el servidor durante el SSR y en el navegador en cada navegación, con el mismo código. Devolver undefined continúa; devolver un Response (una redirección, normalmente) detiene la ruta.
Hay dos clases:
- Global (sin opciones): se ejecuta en todas las rutas, por orden de nombre de archivo.
- Con nombre (desde
1.6,{ global: false }): sólo en las rutas que lo piden conmiddlewareendefinePageo en un+group.ts. El nombre es el del archivo sin.tsni prefijo numérico:10-auth.tses"auth".
Se autoimporta. Es isomórfico: también llega al bundle del navegador.
Uso#
// app/middleware/auth.ts
import { defineRouteMiddleware } from "@meekojs/meeko/framework";
export default defineRouteMiddleware(({ url, phase, request }) => {
// Una cookie pública que indica la sesión: la cookie de sesión de Meeko es HttpOnly.
const cookies = phase === "server" ? request?.headers.get("cookie") ?? "" : document.cookie;
if (!cookies.includes("signed-in=1")) {
const login = new URL("/login", url);
login.searchParams.set("next", url.pathname);
return Response.redirect(login);
}
}, { global: false });Las páginas que lo piden con definePage({ middleware: ["auth"] }) o un +group.ts redirigen a /login sin la cookie.
Tipo#
function defineRouteMiddleware(
middleware: RouteMiddleware,
options?: RouteMiddlewareOptions,
): RouteMiddlewareinterface RouteMiddlewareContext {
readonly url: URL
readonly match: RouteMatch
readonly signal: AbortSignal
readonly phase: 'server' | 'client'
readonly request?: Request
}interface RouteMiddlewareOptions {
readonly global?: boolean
}Parámetros#
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
middleware |
(context: RouteMiddlewareContext) => void | Response | Promise<void | Response> |
obligatorio | La función. Recibe url, match (la ruta y sus params), signal (se aborta si la navegación se cancela), phase ("server" o "client") y request, que sólo existe en el servidor. |
options |
{ global?: boolean } |
global | Desde 1.6. { global: false } lo hace con nombre. Debe ser un objeto literal: Meeko lo lee sin ejecutar el archivo. |
Valor devuelto#
El mismo middleware, para exportarlo por defecto. Lo que hace cada resultado:
| Resultado | En el servidor | En el navegador |
|---|---|---|
undefined |
Continúa con el siguiente middleware y la página | Igual |
Response.redirect(url) |
Responde con la redirección | location.assign con la URL: una carga de página nueva |
Otro Response |
Lo responde | Cancela la navegación y la entrega a onNavigationError |
| Un error | Página de error | Cancela la navegación |
Ejemplos#
Global, limitado a una ruta#
Un middleware global puede decidir por la ruta con match.route.id:
// app/middleware/10-locale.ts
import { defineRouteMiddleware } from "@meekojs/meeko/framework";
export default defineRouteMiddleware(({ url, match }) => {
if (match.route.id !== "/dashboard") return;
if (!url.searchParams.has("locale")) {
const target = new URL(url);
target.searchParams.set("locale", "es");
return Response.redirect(target);
}
});Pedirlo desde un grupo y desde una página#
// app/pages/(dashboard)/+group.ts
import { defineGroup } from "@meekojs/meeko/framework";
export default defineGroup({ layout: "dashboard", middleware: ["auth"] });<!-- app/pages/(dashboard)/admin/users.meeko -->
<script server lang="ts">
export const route = definePage({ middleware: ["admin"] });
</script>
<template><h1>Usuarios</h1></template>/admin/users ejecuta los globales, después auth (del grupo) y después admin (de la página). middleware: false quita los heredados.
Mantenimiento programado#
Un middleware global puede cortar todas las rutas salvo una:
// app/middleware/00-maintenance.ts
import { defineRouteMiddleware } from "@meekojs/meeko/framework";
const until = Date.parse("2026-11-01T06:00:00Z");
export default defineRouteMiddleware(({ url, match }) => {
if (Date.now() < until && match.route.id !== "/maintenance") return Response.redirect(new URL("/maintenance", url));
});Errores comunes#
Pedir un middleware que no existe#
export const route = definePage({ middleware: ["auth", "admn"] });meeko check, meeko dev y meeko build se detienen con el middleware "admn" no existe; crea app/middleware/admn.ts con defineRouteMiddleware(..., { global: false }) (middleware con nombre disponibles: "admin", "auth"), o app/middleware/admn.server.ts con defineServerRouteMiddleware para uno que sólo se ejecuta en el servidor.
export const route = definePage({ middleware: ["auth", "admin"] });Pedir por nombre un middleware global#
// app/middleware/10-locale.ts, sin opciones
export default defineRouteMiddleware(() => {});
// app/pages/(dashboard)/+group.ts
export default defineGroup({ middleware: ["locale"] });el middleware "locale" (…/10-locale.ts) es global y ya se ejecuta en todas las rutas; quítalo de middleware o decláralo con defineRouteMiddleware(..., { global: false }).
export default defineRouteMiddleware(() => {}, { global: false });Usarlo en un *.server.ts#
// app/middleware/auth.server.ts
export default defineRouteMiddleware(() => {}, { global: false });un archivo *.server.ts de app/middleware es middleware de ruta de servidor; decláralo con defineServerRouteMiddleware en lugar de defineRouteMiddleware.
// app/middleware/auth.server.ts
import { redirect } from "@meekojs/meeko/framework";
export default defineServerRouteMiddleware(({ session }) => {
if (!session.current) redirect("/login");
}, { global: false });Confiar en él como autorización#
El middleware de ruta isomórfico viaja al navegador y no lee la sesión (su cookie es HttpOnly). Un usuario puede saltárselo pidiendo los datos a la API: no hay ningún error que lo señale.
// app/middleware/admin.ts: la única comprobación del rol
export default defineRouteMiddleware(({ url }) => {
if (!document.cookie.includes("role=admin")) return Response.redirect(new URL("/", url));
}, { global: false });// app/middleware/admin.server.ts: en el servidor, con la sesión
export default defineServerRouteMiddleware(({ session, url }) => {
if ((session.current?.data as { role?: string } | undefined)?.role !== "admin") return Response.redirect(new URL("/", url));
}, { global: false });Y en la API, auth: "required" y la comprobación del rol en el handler.
Notas#
- Servidor y navegador. El mismo archivo se ejecuta en el SSR (
phase: "server", conrequest) y en la navegación del cliente (phase: "client", sinrequest). No importesserver/ni secretos: viajan en el bundle. La hidratación no lo ejecuta: el servidor ya lo hizo. - Orden. Primero los globales, por nombre de archivo; después los con nombre de la ruta, en el orden declarado (grupos de fuera hacia dentro y la página); y después los de servidor (
*.server.ts). El primero que devuelve unResponsedetiene la ruta. - Bundle. Los globales van con la entrada del navegador; cada middleware con nombre es un chunk que se carga la primera vez que se navega a una de sus rutas.
- Seguridad. Mejora la navegación (redirige sin pedir nada), pero nunca sustituye a la autorización del loader, la API o el RPC. Para proteger una página con la sesión, usa
defineServerRouteMiddleware. - Exclusiones. Los
*.test.ts,*.spec.ts,*.d.tsy*.client.tsdeapp/middlewareno son middleware. Sólo cuentaapp/middlewaredel proyecto, no el de los layers.