@k8ordo/framework

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

text
src/routes/
  guard.ts
  layout.tsx
  page.tsx
  admin/
    guard.ts
    page.tsx
    [id]/
      page.tsx

A 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.ts
import { 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.

Information

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.ts
import { 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 HEAD for that page
  • a Server Action posted to that page
  • a route.ts or a not-found.tsx below it

The root guard.ts also runs for a URL no route matched.

Information

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.

k8ordo

React libraries that use Baseline features without holding back.

© 2026 k8o — MIT License

Typeset in Noto Sans JP & M PLUS 2