Access Policies
Attach auth requirements to a route or group so denied users never see a flash of protected UI.
Example
Return an explicit allow, redirect, unauthorized, or forbidden decision from the route policy and repeat the enforcement on server APIs.
route('/admin', AdminPage, {
policies: [({ auth }) =>
auth.principal?.permissions?.includes('admin') ? allow() : forbidden()],
});Policy decisions
A RoutePolicy is a function from RouteContext to an AccessDecision, returned either synchronously or as a promise. There are exactly three decision shapes: allow(), redirect(to, init?), and deny(status), and helper functions unauthorized(), forbidden(), and notFound() cover the common deny statuses (401, 403, 404) without you having to remember the numbers. Policies attach to a route, a page(), or a whole group() via CommonAccessOptions.policies, and a group's policies run for every route nested inside it in addition to whatever the specific route adds.
Authentication context
currentAuth() returns the AuthContext resolved for the route currently rendering, and it's the same object available as context.auth inside a policy or loader. Resolving that context is configurable per-app through RouteAuthOptions.resolve, a RouteAuthResolver that runs before policies and produces the AuthContext they'll see — so the actual mechanism (cookie, token, session lookup) lives in one place rather than being re-implemented in every policy. RouteRegistryOptions.auth sets this resolver at the registry boundary, and individual routes can still opt into stricter requirements via CommonAccessOptions.auth.
Redirects and denials
redirect(to, init?) returns an AccessRedirectDecision with a configurable HTTP status (301, 302, 303, 307, or 308) and an optional replace flag so the redirect doesn't leave the denied URL in browser history. RouteAuthOptions.loginPath and authenticatedRedirectTo cover the two most common redirect cases directly — where to send an unauthenticated user, and where to send an already-authenticated user who hits a route like a login page — either as a static path or a function of the request context. A deny() decision, by contrast, renders in place with the given status rather than sending the browser anywhere.
Server enforcement
Because policies run inside resolveRouteRequest() before a RouteRenderResult is produced, the same access rules apply whether the request is being resolved for SSR, SSG, or client-side navigation — a policy isn't a client-only convenience that a curious user can bypass by hitting the server route directly. RouteRequestOptions.request carries the actual Request object through to loaders and policies during SSR, so server-only decisions (cookies, headers, IP) are available exactly where they're evaluated, not bolted on afterward.