Guards
Server mode only. A guard.ts can stop a request before the page renders, send the visitor to the sign-in page, or add headers to the response.
On this page
Placement
src/routes/
guard.ts
layout.tsx
page.tsx
admin/
guard.ts
page.tsx
[id]/
page.tsxA guard.ts can sit in a directory at any level. It runs before a request for any URL below that directory is handled. When several lie along a URL, they run outer first, one at a time.
Opening /admin/42 with this layout runs the root guard.ts, then admin/guard.ts. The page renders only when both let the request through.
Return value
src/routes/admin/guard.tsimport { href } from '@k8ordo/framework';
import { cookies } from '@k8ordo/framework/server';
import type { Guard } from '@k8ordo/framework/server';
const guard: Guard<'/admin'> = () => {
if (cookies().has('session')) return;
Returning nothing lets the request through
return new Response(null, {
A `Response` ends the request
status: 303,
headers: { location: href('/login') },
`href()` adds Vite’s `base`
});
};
export default guard;A returned Response is sent as the response, whether a redirect, a 401 or a 403. Guards further in and the page do not run.
A guard receives { request, params }. request is the Request as it arrived. params are the parameters of the directory’s pattern, still the raw strings from the URL.
The type is Guard from @k8ordo/framework/server, with the directory’s pattern as its type argument. The generated route table checks every guard.ts against that pattern as well.
Note
Returning anything but a Response or undefined is an error: a guard.ts returns a Response to end the request, or nothing to let it through.
Response headers
src/routes/guard.tsimport { responseHeaders } from '@k8ordo/framework/server';
export default function guard() {
responseHeaders().set('x-content-type-options', 'nosniff');
}responseHeaders() returns the Headers of the final response. Headers set on it are added even when the guard lets the request through, whether the response is the page, its payload, the not-found, or a Response a guard further in returned. A header with the same name is replaced.
responseHeaders() can be called only inside a guard.ts, a route.ts or a Server Action. Called in a page render, it fails with an error that starts responseHeaders() belongs to what answers the request. Called outside a request, the error starts responseHeaders() needs a request.
The Content-Security-Policy header is written in the root guard.ts too. See CSP.
Rewriting after the render
There is no next() that runs the page and hands its answer back to be rewritten. A page streams, and its headers are sent before its body is finished. A guard decides before the page starts rendering.
Covered requests
These requests below its directory are covered:
- a page, and its payload fetched by a client navigation
- a
HEADfor that page - a Server Action posted to that page
- a
route.tsor anot-found.tsxbelow it
The root guard.ts also runs for a URL no route matched.
Note
A guard does not protect a Server Action itself. An action is a function any page can call, and it is posted to the URL of the page that calls it. Check what the action needs inside the action as well.
Execution order
A guard runs after the paramsSchema exports have matched the URL, and before the Server Action and the render. A redirect.ts answers before any guard. A guard under [locale] already has the locale the URL names, and a Server Action after it can read the cookies the guard wrote.