Request and cookies
Server mode only. Read the request’s headers and cookies from a page, and write cookies from a guard.ts, a route.ts or a Server Action.
On this page
Reading request
src/routes/layout.tsximport type { LayoutProps } from '@k8ordo/framework';
export default function RootLayout({
children,
request,
}: LayoutProps<'/'>) {
const theme = request.cookies.get('theme') ?? 'light';
Reads a cookie by name
return (
<html data-theme={theme} lang="en">
<body>{children}</body>
</html>
);
}A page, a layout and a not-found.tsx receive request alongside params and pathname.
request.headers: the request’s headers, asHeaders.request.cookies: theCookieheader read by name into aReadonlyMap. The first of a repeated name wins. Surrounding quotes are removed and the value is percent-decoded.
The generated register.gen.ts adds request to PageProps and LayoutProps under server mode only. Under static mode, a page that reads request is a type error.
src/components/greeting.tsximport type { RouteRequest } from '@k8ordo/framework/server';
export function Greeting({ request }: { request: RouteRequest }) {
const name = request.cookies.get('name') ?? 'there';
return <p>Hello, {name}</p>;
}To pass request to a child Server Component, type the prop with RouteRequest from @k8ordo/framework/server. Headers cannot be passed to a Client Component, so give a Client Component only the values it needs. The query is not part of request. A page declares the query it reads by exporting search, as described on Parameters.
Reading and writing with cookies()
src/lib/sign-in.ts'use server';
import { href } from '@k8ordo/framework';
import { cookies, redirect } from '@k8ordo/framework/server';
import { startSession } from './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, {
Sent as `Set-Cookie` on the response
maxAge: 60 * 60 * 24 * 30,
});
redirect(href('/account'));
}Inside a guard.ts, a route.ts or a Server Action, cookies() from @k8ordo/framework/server reads and writes cookies. The action above writes a session cookie after a successful sign-in, then redirects to /account.
A get reflects whatever was set or deleted earlier in the same request, so a Server Action reads what a guard.ts before it wrote. Every set goes out as a Set-Cookie on whichever response answers: the page re-rendered after an action, the 303 of a redirect(), or the payload the client runtime applies.
A value is percent-encoded when written to Set-Cookie and decoded when the next request brings it back. Pass set the raw string. An already encoded value is encoded twice.
Note
In the page re-rendered after an action, request.cookies holds only the request’s cookies. What the action wrote is not in it.
Cookie attributes
cookies().set('session', token, { maxAge: 3600, sameSite: 'strict' });Pass the cookie’s attributes as the third argument of set. An attribute left out takes a default suited to a sign-in session cookie.
path:/by default.domain: not set by default.maxAge: the lifetime in seconds.0expires the cookie at once.expires: the expiry, as aDate.httpOnly:trueby default, so page scripts cannot read the cookie.secure:trueby default, so the cookie is sent over HTTPS only.sameSite:'strict','lax'or'none','lax'by default. With'none',securedefaults totrueeven onlocalhost. Combining it withsecure: falsefails witha cookie with sameSite "none" has to be secure.
Only for a request over plain HTTP to localhost, 127.0.0.1 or [::1] does secure default to false: Chromium and Firefox keep a Secure cookie there, but Safari does not. To serve any other host over plain HTTP, pass secure: false.
Pass delete the path and domain the cookie was written with. A browser tells cookies apart by those two.
Cookie state from @k8ordo/state
src/routes/layout.tsximport type { LayoutProps } from '@k8ordo/framework';
import { Shell } from '../components/shell';
import { density } from '../state';
export default function RootLayout({
children,
request,
}: LayoutProps<'/'>) {
return (
<Shell initialCookie={density.parseCookies(request.cookies)}>
Reads the values through the schema, once
{children}
</Shell>
);
}Cookie state from @k8ordo/state is read by passing request.cookies to the definition’s parseCookies. Pass the result to useAppState as initialCookie, and the server render already uses them.
initialCookie seeds only the useAppState call it is passed to. Read the cookie once in a layout, and pass the result down to the Client Component as a prop. Defining the state is described on Cookie.
src/lib/compact.ts'use server';
import { cookies } from '@k8ordo/framework/server';
import { density } from '../state';
export async function compact() {
cookies().set(
density.cookieName,
density.cookieValue({ density: 'compact' }),
{ httpOnly: false, maxAge: 34_560_000 },
The attributes the browser writes
);
}To write the same cookie with cookies(), pass cookieName as the name and the return value of cookieValue() as the value. cookieValue() runs the values through the schema and fills the fields you left out with their defaults. Set maxAge to 400 days (34_560_000 seconds), the same the browser writes.
Pitfall
Keep httpOnly: false. A script cannot read an HttpOnly cookie, so useAppState in the browser would no longer see the value.
requestHeaders()
src/lib/report.ts'use server';
import { requestHeaders } from '@k8ordo/framework/server';
import { saveReport } from './reports.server';
export async function report(formData: FormData): Promise<void> {
const agent = requestHeaders().get('user-agent') ?? 'unknown';
Reads the request’s headers
await saveReport(formData.get('body'), agent);
}A Server Action receives only its arguments, not the request. Read the headers with requestHeaders().
Like cookies(), requestHeaders() works only inside a guard.ts, a route.ts or a Server Action. A guard.ts and a route.ts receive the Request itself, so they usually read request.headers instead.
Writing to the response
A page has no way to write a status or a Set-Cookie. Only a guard.ts, or the Server Action a POST called, can write them, and both run before the page renders. Calling cookies() or responseHeaders() while a page renders fails with an error containing belongs to what answers the request.