camelon

Middleware

A directory can hold a _middleware.ts. It applies to that directory and everything under it. Pipelines run outermost first, then the loader or action. Each function gets the same args bag as a loader.

import { redirect } from 'camelon';
import type { MiddlewareFunction } from 'camelon';

const requireAuth: MiddlewareFunction = ({ session }) => {
  if (!session.has('userId')) throw redirect('/login');
};

export const middleware = [requireAuth];
export default function Dashboard() {
  return <h1>Welcome back</h1>;
}

The _middleware.ts guard runs before Dashboard.tsx. If the session is empty it throws redirect, and the page never renders.

Guarding

Throw to stop the request before the loader runs:

throw redirect('/login');
throw notFound();

Passing data down

A value returned from middleware merges into ctx, the per-request scratch shared with the loader. It replaces Express's res.locals.

// _middleware.ts
export const middleware = [
  ({ request }) => ({ tenant: tenantFrom(request) }), // → ctx.tenant
];
export function loader({ session, ctx }) {
  return { userId: session.get<string>('userId'), tenant: ctx.tenant };
}

Read the session from session. Read middleware values from ctx.