Middleware
Middleware in λ Router are React components that wrap route content. They receive children as a prop and can render them conditionally, add layout elements, or provide context. Middleware is the recommended pattern for auth guards, layout shells, and any cross-cutting concerns.
Defining Middleware
Section titled “Defining Middleware”A middleware is any React component that accepts children and renders them (or not):
import type { MiddlewareProps } from "@studiolambda/router/react";
function AuthGuard({ children }: MiddlewareProps) { const user = useCurrentUser();
if (!user) { return <LoginPage />; }
return children;}The MiddlewareProps type is simply { children: ReactNode }.
Applying Middleware
Section titled “Applying Middleware”Use the .middleware() method on the route builder. It accepts an array of middleware components:
import { createRouter } from "@studiolambda/router/react";
const router = createRouter(function (route) { route("/").render(Home);
route("/dashboard") .middleware([AuthGuard]) .render(Dashboard);
route("/admin") .middleware([AuthGuard, AdminOnly]) .render(AdminPanel);});When multiple middleware are specified, they are applied from outermost to innermost — the first element in the array wraps the outermost:
// For .middleware([AuthGuard, AdminOnly]):// Renders as:<AuthGuard> <AdminOnly> <AdminPanel /> </AdminOnly></AuthGuard>Middleware with Groups
Section titled “Middleware with Groups”Groups share middleware across multiple routes. Parent group middleware wraps outermost, followed by child route middleware:
const router = createRouter(function (route) { route("/").render(Home);
const authed = route().middleware([AuthGuard]).group();
authed("/profile").render(Profile); authed("/settings").render(Settings);
// Nested groups inherit parent middleware. const admin = authed("/admin").middleware([AdminOnly]).group();
admin("/users").render(AdminUsers); // AuthGuard → AdminOnly → AdminUsers admin("/config").render(AdminConfig); // AuthGuard → AdminOnly → AdminConfig});Navigating to /admin/users renders:
<AuthGuard> <AdminOnly> <AdminUsers /> </AdminOnly></AuthGuard>Layout Middleware
Section titled “Layout Middleware”Middleware is a natural way to implement shared layouts:
function DashboardLayout({ children }: MiddlewareProps) { return ( <div className="dashboard"> <DashboardSidebar /> <main>{children}</main> </div> );}
const router = createRouter(function (route) { const dashboard = route("/dashboard") .middleware([AuthGuard, DashboardLayout]) .group();
dashboard("/").render(DashboardHome); dashboard("/analytics").render(Analytics); dashboard("/settings").render(Settings);});Every route under /dashboard renders inside the shared sidebar layout, and only authenticated users can access it.
Conditional Rendering
Section titled “Conditional Rendering”Middleware can conditionally render children based on any logic — permissions, feature flags, data availability:
function FeatureFlag({ children }: MiddlewareProps) { const flags = useFeatureFlags();
if (!flags.newDashboard) { return <LegacyDashboard />; }
return children;}
function RoleGuard({ children }: MiddlewareProps) { const user = useCurrentUser();
if (user.role !== "admin") { return <Forbidden />; }
return children;}Providing Context
Section titled “Providing Context”Middleware can provide context values to descendant components:
const ThemeContext = createContext("light");
function ThemeProvider({ children }: MiddlewareProps) { const theme = useUserThemePreference();
return <ThemeContext value={theme}>{children}</ThemeContext>;}
const router = createRouter(function (route) { const app = route().middleware([ThemeProvider]).group();
app("/").render(Home); app("/settings").render(Settings);});Middleware with Suspense
Section titled “Middleware with Suspense”Middleware components can include their own Suspense boundaries for fine-grained loading states:
function DataLayout({ children }: MiddlewareProps) { return ( <div className="layout"> <Header /> <Suspense fallback={<ContentSkeleton />}>{children}</Suspense> <Footer /> </div> );}This way, the header and footer remain visible while the route content is loading.