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.