Read the request and cookies
In this mode a page can read the request’s headers and cookies. Writing a cookie is not the page’s job, though: it belongs to what answers the request, a guard.ts, a route.ts or a Server Action. This page covers reading first, then writing.
On this page
Read the request from a page
A page, a layout and a not-found.tsx receive request beside params and pathname: the request’s headers, and its cookies read by name.
src/routes/layout.tsximport type { LayoutProps } from '@k8ordo/router';
export default function RootLayout({
children,
request,
}: LayoutProps<'/'>) {
const theme = request.cookies.get('theme') ?? 'light';
return (
<html data-theme={theme} lang="en">
<body>{children}</body>
</html>
);
}request.headers: the request’s headers, asHeaders.request.cookies: theCookieheader read by name into aReadonlyMap. The first of a repeated name wins, and surrounding quotes are removed before the value is URL-decoded.
PageProps and LayoutProps carry request because the generated register.gen.ts says this mode has one. To hand it to a component further down as a prop, type it with RouteRequest from @k8ordo/server/runtime.
src/routes/_parts/greeting.tsximport type { RouteRequest } from '@k8ordo/server/runtime';
export function Greeting({ request }: { request: RouteRequest }) {
const name = request.cookies.get('name') ?? 'there';
return <p>Hello, {name}</p>;
}Do not hand request whole to a client component; take out the values it needs. Headers cannot cross the boundary.
The search is not in request. Everything after the ? belongs to @k8ordo/state, and is read in the browser.
Read and write cookies with cookies()
Inside a guard.ts, a route.ts or a Server Action, cookies() from @k8ordo/server/runtime reads and writes cookies. Below, an action that writes a session cookie on a successful sign-in, then sends the visitor to their account.
src/routes/login/_parts/sign-in.ts'use server';
import { href } from '@k8ordo/router';
import { cookies, redirect } from '@k8ordo/server/runtime';
import { startSession } from '../../_data/sessions.server';
export type SignInState = { error?: string };
export async function signIn(
_previous: SignInState,
formData: FormData,
): Promise<SignInState> {
const session = await startSession(formData);
if (session === null) return { error: 'Wrong password' };
cookies().set('session', session.token, {
maxAge: 60 * 60 * 24 * 30,
});
redirect(href('/account'));
}A read sees the cookies the request carried, with what was written earlier in the same request on top, so a Server Action reads what a guard before it wrote.
Every write reaches the browser as a Set-Cookie on the answer, whatever the answer is: the page an action re-rendered, or the 303 of a redirect().
A value is percent-encoded on its way into Set-Cookie and decoded when a request brings it back, so set takes any string as it is. An already encoded value would be encoded twice.
Note
The page re-rendered after an action sees, in request.cookies, the cookies the request carried — not what the action wrote.
Choose the cookie’s attributes
Pass the cookie’s attributes as the third argument of set. Left out, they are what a session wants.
path:/by default.domain: not set by default.maxAge: how long it lasts, in seconds.0expires it at once.expires: when it expires, as aDate.httpOnly:trueby default, so a page’s script cannot read it.secure:trueby default, so it is sent over HTTPS only.sameSite:'strict','lax'or'none','lax'by default.'none'only goes withsecure.
Over plain HTTP to this machine (localhost, 127.0.0.1, [::1]), though, secure defaults to false: Chromium and Firefox keep a Secure cookie there, but Safari drops it.
Anywhere else served over plain HTTP, pass secure: false.
Pass delete the path and domain the cookie was written with, since a browser tells cookies apart by those.
Read the request’s headers in a Server Action
A Server Action receives its arguments, not the request. Inside one, read the headers with requestHeaders().
src/routes/_parts/report.ts'use server';
import { requestHeaders } from '@k8ordo/server/runtime';
import { saveReport } from '../_data/reports.server';
export async function report(formData: FormData): Promise<void> {
const agent = requestHeaders().get('user-agent') ?? 'unknown';
await saveReport(formData.get('body'), agent);
}Like cookies(), requestHeaders() works only inside a guard.ts, a route.ts or a Server Action. A guard and a route.ts receive request, so they usually read that instead.
Why a page does not write the response
A page has no way to write a status or a Set-Cookie. A page is a render, and a render that wrote the response would be a second handler.
What the answer carries beyond the page is decided before it, by a guard.ts, or by the Server Action a POST carries. Calling cookies() or responseHeaders() while a page renders throws.
request exists only in this mode. Under @k8ordo/static the generated types do not carry it, so a page that reads it fails to type-check.