Skip to content
Meeko
Type to search the guides and the API reference. Open it from any page with / or Ctrl K; move with the arrow keys and open a result with Enter.
Get started

Contents

On this page

defineServerRouteMiddleware

Declares route middleware running only on the server, with the request's session, before the page's server code.

macro Since 1.6.0 No import @meekojs/meeko/framework Source code

Se autoimporta en el código de servidor: server/ y <script server>.

ts
import { defineServerRouteMiddleware } from "@meekojs/meeko/framework";

defineServerRouteMiddleware declares the route middleware exported by default from an app/middleware/*.server.ts file. It only runs on the server, with the request's session, and never reaches the browser bundle: it can import server/, read secrets and query the database.

Use it to express “this group of pages requires a session” without repeating the check in each <script server>. Without options it is global; with { global: false }, it is named middleware (auth.server.ts is "auth") requested by routes through middleware. It is auto-imported in *.server.ts and server/.

Usage#

ts
// app/middleware/auth.server.ts
import { defineServerRouteMiddleware, redirect } from "@meekojs/meeko/framework";

export default defineServerRouteMiddleware(({ session, url }) => {
  if (!session.current) redirect(`/login?next=${encodeURIComponent(url.pathname)}`);
}, { global: false });
ts
// app/pages/(dashboard)/+group.ts
import { defineGroup } from "@meekojs/meeko/framework";

export default defineGroup({ layout: "dashboard", middleware: ["auth"] });

Each (dashboard) page redirects to /login without a session, on document load and every browser navigation.

Type#

ts
function defineServerRouteMiddleware<TServices = unknown, TEnv = unknown>(
  middleware: ServerRouteMiddleware<TServices, TEnv>,
  options?: RouteMiddlewareOptions,
): ServerRouteMiddleware<TServices, TEnv>

ServerRouteMiddlewareContext

ts
interface ServerRouteMiddlewareContext<TServices = unknown, TEnv = unknown> {
	readonly url: URL
	readonly match: RouteMatch
	readonly params: Readonly<Record<string, string>>
	readonly search: Readonly<Record<string, string | string[]>>
	readonly request: Request
	readonly signal: AbortSignal
	readonly context: RequestContext<TServices, TEnv>
	readonly session: RequestContext<TServices, TEnv>['session']
	readonly services: ContextServices<TServices>
	readonly platform: ServerPlatform<TEnv>
	readonly requestId: string
}

Parameters#

Parameter Type Default Description
middleware (context: ServerRouteMiddlewareContext) => void | Response | Promise<void | Response> required The function. Receives the usePage() context (url, params, search, request, signal, context) and direct access to session, services, platform, requestId and match.
options { global?: boolean } global { global: false } makes it named. Must be an object literal.

params and search come from the URL, not yet validated by page schemas. defineServerRouteMiddleware<Services> types services; since 1.7, without a type argument, services uses the type Meeko infers from server/runtime.ts.

Return value#

The same middleware, for default export. Short-circuits the route like <script server>:

Returning undefined continues.

Examples#

A role, with application services#

ts
// app/middleware/admin.server.ts
import { defineServerRouteMiddleware, notFound } from "@meekojs/meeko/framework";

interface Services { readonly roles: { of(userId: string): Promise<string[]> } }

export default defineServerRouteMiddleware<Services>(async ({ session, services }) => {
  const userId = (session.current?.data as { userId?: string } | undefined)?.userId;
  if (!userId || !(await services.roles.of(userId)).includes("admin")) notFound();
}, { global: false });

Responding with 404 instead of 403 does not reveal that the page exists.

Global: log every page#

Without options, it runs on all routes with server data:

ts
// app/middleware/10-audit.server.ts
import { defineServerRouteMiddleware } from "@meekojs/meeko/framework";

export default defineServerRouteMiddleware(({ requestId, url, context }) => {
  console.info(`[${requestId}] ${url.pathname} from ${context.clientAddress ?? "unknown"}`);
});

Alongside isomorphic middleware#

The isomorphic middleware (app/middleware/signed-in-hint.ts) redirects in the browser without requesting anything; the server middleware (auth.server.ts) protects data. A group can request both; isomorphic middleware always runs first:

ts
// app/pages/(dashboard)/+group.ts
import { defineGroup } from "@meekojs/meeko/framework";

export default defineGroup({ middleware: ["signed-in-hint", "auth"] });

Common mistakes#

Forget to import redirect#

redirect and notFound are available without imports in <script server>, but not in app/middleware/*.server.ts. This is deliberate: auto-import types are global declarations for the entire TypeScript server project, including server/api. Declaring them for middleware would also declare them in APIs, where there is no current page and they would fail at runtime with MEEK073 instead of giving this type error.

Incorrectts
// app/middleware/auth.server.ts
export default defineServerRouteMiddleware(({ session }) => {
  if (!session.current) redirect("/login");
}, { global: false });

meeko check reports TS2304: Cannot find name 'redirect'.

Correctts
// app/middleware/auth.server.ts
import { redirect } from "@meekojs/meeko/framework";

export default defineServerRouteMiddleware(({ session }) => {
  if (!session.current) redirect("/login");
}, { global: false });

Use it in a file without .server#

Incorrectts
// app/middleware/auth.ts
export default defineServerRouteMiddleware(() => {}, { global: false });

defineServerRouteMiddleware sólo se admite en un archivo *.server.ts de app/middleware, que nunca llega al navegador; renombra el archivo a auth.server.ts.

Correctts
// app/middleware/auth.server.ts
export default defineServerRouteMiddleware(() => {}, { global: false });

A helper in app/middleware/*.server.ts#

Since 1.6, every *.server.ts in app/middleware is server middleware.

Incorrectts
// app/middleware/cookies.server.ts
export function parseCookies(header: string) { return header.split("; "); }

un middleware de ruta de servidor debe exportar por defecto defineServerRouteMiddleware(...); desde 1.6, los archivos *.server.ts de app/middleware son middleware de ruta de servidor, así que mueve los helpers a server/utils.

Correctts
// server/utils/cookies.ts
export function parseCookies(header: string): string[] { return header.split("; "); }

Request it on a prerendered or "client" page#

These pages do not request their data from the server on each visit: there is no session to check.

Incorrectts
export const route = definePage({ render: "prerender", middleware: ["auth"] });

el middleware de servidor "auth" (…/auth.server.ts) necesita una página con datos del servidor, pero …/reports.meeko usa render: "prerender" y se renderiza al hacer el build, sin la petición ni la sesión de cada visitante. Usa render: "ssr" o "stream", o quítalo de esa página con middleware: false.

Correctts
export const route = definePage({ render: "ssr", middleware: ["auth"] });

Notes#

  • When it runs. On the document request and, during navigation, on the page data request, before <script server> or load; in render: "stream", before the first byte. A redirect reaches the browser like a page's redirect().
  • Order. First isomorphic middleware (global and named), then server middleware (global and named, in declared order). The first to short-circuit stops the route.
  • Serverless pages. Does not run in render: "client" or "prerender"; global server middleware does not either, including on initial load. The static adapter warns that it does not run them.
  • Security. Never reaches the browser: the Vite plugin rejects any *.server.* in its graph. But it only protects pages: server/api and RPC need their own guards (auth: "required"), even if only protected pages call them.
  • Cache. With cache in definePage (since 1.7), middleware always runs before 304 or the in-memory copy.
  • Names. Named auth.ts and auth.server.ts together are a duplicate name and stop discovery.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters

Edit on GitHub: docs/en/reference/macros/defineserverroutemiddleware.md