newX 1.3: islands to disk, server-mode islands, and an image proxy

Middleware

Middleware

Route-level middleware lets you intercept page requests before they reach the page handler. Use it for authentication, redirects, logging, and validation.

The _middleware.ts convention

Place a _middleware.ts file in any route directory. It runs for all routes in that directory and its subdirectories.

file tree
pages/
_middleware.ts -> runs for all routes
index.tsx
dashboard/
_middleware.ts -> runs only for /dashboard/*
settings.tsx
profile.tsx
admin/
_middleware.ts -> auth check for /admin/*
index.tsx

Middleware context

A middleware function receives a context object with params (dynamic route params) and request (the original Request), plus a next function as the second argument to continue the chain. It returns a Response.

src/pages/_middleware.ts
import type { MiddlewareContext, MiddlewareNext } from "@thexjs/core";export async function middleware(ctx: MiddlewareContext, next: MiddlewareNext) {  console.log(`[${ctx.request.method}] ${ctx.request.url}`);  return next();}

Auth middleware example

A common use case is checking for an auth cookie and redirecting unauthenticated users.

src/pages/dashboard/_middleware.ts
import type { MiddlewareContext, MiddlewareNext } from "@thexjs/core";export async function middleware(ctx: MiddlewareContext, next: MiddlewareNext) {  const session = ctx.request.headers.get("cookie");  if (!session) {    return new Response(null, {      status: 302,      headers: { Location: "/login" },    });  }  const user = await validateSession(session);  if (!user) {    return new Response(null, {      status: 302,      headers: { Location: "/login" },    });  }  return next();}

MiddlewareNext

Call next() (no arguments) to pass control to the next middleware or the route handler. Any mutations to ctx.params you make before the call flow through to downstream handlers.

Middleware applies to page routes only. API routes (in apiDir) and content routes are dispatched without a middleware chain.

Route-level export const middleware

Instead of a separate _middleware.ts file, you can export middleware directly from a page file. This is useful when the middleware is short and tightly coupled to one route.

src/pages/dashboard.tsx
import type { RouteProps, MiddlewareContext, MiddlewareNext } from "@thexjs/core";export async function middleware(ctx: MiddlewareContext, next: MiddlewareNext) {  const session = ctx.request.headers.get("cookie");  if (!session) return new Response(null, { status: 302, headers: { Location: "/login" } });  return next();}export default function Dashboard({}: RouteProps) {  return <h1>Welcome back</h1>;}

When both a _middleware.ts file and an inline export const middleware exist in the same directory, the file-level middleware runs first, then the route-level one. The same MiddlewareContext and MiddlewareNext types apply to both variants.

composeMiddleware

The _middleware.ts convention builds a single handler under the hood, but you can also wire middleware yourself with composeMiddleware(fns, handler). It folds an array of middleware functions into one onion-style chain that reaches handler only after every next() has been called.

src/middleware.ts
import { composeMiddleware } from "@thexjs/core";import type { MiddlewareContext } from "@thexjs/core";function enforceAuth(ctx: MiddlewareContext, next: () => Promise<Response>) {  const session = ctx.request.headers.get("cookie");  if (!session) return new Response(null, { status: 302, headers: { Location: "/login" } });  return next();}function attachUser(ctx: MiddlewareContext, next: () => Promise<Response>) {  ctx.params.role = "viewer";  return next();}const wrapped = composeMiddleware(  [enforceAuth, attachUser],  (ctx) => new Response(`role: ${"$"}{ctx.params.role}`),);

Calling next() more than once in the same middleware throws ("next() called multiple times"), which catches double-dispatch bugs at runtime.

Redirect patterns

Return a Response with a 302 status and a Location header to redirect. You can also return JSON responses for API middleware validation errors.

redirect example
// Redirect to loginreturn new Response(null, {  status: 302,  headers: { Location: "/login?redirect=" + ctx.request.url },});// Redirect back after successful authconst url = new URL(ctx.request.url);const redirectTo = url.searchParams.get("redirect") || "/";return new Response(null, {  status: 302,  headers: { Location: redirectTo },});

Composing middleware

Multiple folder middleware files run onion-style in directory order. To chain middleware inside a single file, use composeMiddleware from @thexjs/core:

composing
import { composeMiddleware } from "@thexjs/core";export const middleware = composeMiddleware(  [withLogging, withAuth],  async (ctx) => renderPage(ctx),);