--- slug: request-context title: Request Context description: Read request headers and pass request-scoped data to server components and server functions. category: Runtime and Middleware order: 10 --- ## When to Use Request Context Waku provides request-scoped context for server code. Use it when a server component or server function needs information from the current request. Middleware runs before the render scope is established, so middleware reads the request from its Hono context instead. ## Read the Current Request Use `getRequest` in server-only code to access the current `Request`: ```tsx import { getRequest } from 'waku/router/server'; export default function Page() { const userAgent = getRequest().headers.get('user-agent') ?? 'unknown'; return

User agent: {userAgent}

; } export const getConfig = async () => ({ render: 'dynamic' }) as const; ``` Header names are case-insensitive, and `headers.get()` returns `null` for a header the request does not have. `getRequest()` throws if request context is not available. Call it during request handling, not in module scope. `getRequest().url` is the URL the request was sent to. After a client navigation, that is Waku's RSC URL (such as `/RSC/R/about.txt`), not the page URL. A page receives its params and `query` as props; elsewhere, read the route with `unstable_parseRouterRequest(getRequest())`. A server function called from the client sends its own request, which `unstable_parseRouterRequest` reports only as `{ type: 'action' }`, so pass the function any route data it needs as arguments. `getRequest()` returns the original incoming request. Inside an API route handler, the handler's own `req` argument may have a rewritten URL (the route path with any prefix stripped), so `req.url` and `getRequest().url` can differ. Use the handler argument for routing-specific values and for the body, which it shares with `getRequest()`. `getRequest` is exported from `waku/router/server` and is available wherever the router runs your code: renders (request and build), server functions, API route handlers, interceptors, and a custom router's `resolve`. In the Minimal API, use `input.req` from `handleRequest` instead, and bring your own `AsyncLocalStorage` if you need it deeper in the tree. Components rendered as `static` are prerendered at build time, so do not read `getRequest`, headers, or interceptor-seeded state in a static page, layout, or slice; those values would be baked into the prerendered output. Read request-scoped data only in `dynamic` rendering. File-system pages, layouts, and slices are static by default, so the page examples in this guide export `getConfig` with `render: 'dynamic'`. Only the file-system router reads `getConfig`; with `createPages`, pass `render: 'dynamic'` to `createPage` instead. (During a static route build `getRequest().url` is the route's path, but a static slice build uses a placeholder root request that does not identify the slice.) ## Pass Data with an Interceptor Waku's context is request-only. To share derived, request-scoped data with server components and server functions, bring your own `AsyncLocalStorage` and seed it from a handler interceptor. An interceptor wraps each render in both the request and build phases, so `getRequest` is available inside it. Define your own store: ```ts // src/lib/request-data.ts import { AsyncLocalStorage } from 'node:async_hooks'; export type RequestData = { country?: string; }; const store = new AsyncLocalStorage(); export const getRequestData = (): RequestData => store.getStore() ?? {}; export const runWithRequestData = ( data: RequestData, fn: () => Promise, ) => store.run(data, fn); ``` In managed mode (no `waku.server.tsx`), seed the store from an interceptor in `src/pages/_interceptors/request-data.ts`: ```ts import type { HandlerInterceptor } from 'waku/router/server'; import { getRequest } from 'waku/router/server'; import { runWithRequestData } from '../../lib/request-data.js'; const requestDataInterceptor: HandlerInterceptor = (next) => { const country = getRequest().headers.get('cf-ipcountry') ?? 'unknown'; return runWithRequestData({ country }, next); }; export default requestDataInterceptor; ``` With a custom `waku.server.tsx` using `createPages`, register the same logic through `createInterceptor`: ```ts import { getRequest } from 'waku/router/server'; import { runWithRequestData } from './lib/request-data.js'; createPages(async ({ createPage, createInterceptor }) => { createInterceptor((next) => { const country = getRequest().headers.get('cf-ipcountry') ?? 'unknown'; return runWithRequestData({ country }, next); }); return [ // ...pages... ]; }); ``` Then read that data from server code: ```tsx import { getRequestData } from '../lib/request-data.js'; export default function Page() { const { country } = getRequestData(); return

Country: {country ?? 'unknown'}

; } export const getConfig = async () => ({ render: 'dynamic' }) as const; ``` If the value is just a request header, you can also read it from `getRequest().headers` in the component without a store. Reach for an interceptor when you want to compute the value once per request and share it across the render. ## Cookies Waku does not currently provide a dedicated cookie API. Read request cookies from `getRequest().headers` in server code, and use middleware to set response cookies after rendering. Read a request cookie from server code with the `cookie` package: ```tsx import * as cookie from 'cookie'; import { getRequest } from 'waku/router/server'; export default function Page() { const cookies = cookie.parseCookie(getRequest().headers.get('cookie') ?? ''); return

Session: {cookies.sessionId ?? 'none'}

; } export const getConfig = async () => ({ render: 'dynamic' }) as const; ``` Set a response cookie from middleware, which owns the response: ```ts // src/middleware/session.ts import * as cookie from 'cookie'; import type { MiddlewareHandler } from 'hono'; const sessionMiddleware = (): MiddlewareHandler => { return async (c, next) => { const cookies = cookie.parseCookie(c.req.header('cookie') || ''); const sessionId = cookies.sessionId; await next(); if (c.res && sessionId) { const headers = new Headers(c.res.headers); headers.append( 'set-cookie', cookie.stringifySetCookie({ name: 'sessionId', value: sessionId, httpOnly: true, path: '/', sameSite: 'lax', secure: true, }), ); c.res = new Response(c.res.body, { status: c.res.status, statusText: c.res.statusText, headers, }); } }; }; export default sessionMiddleware; ``` Keep any request-scoped store small and request-specific. Do not store values that should outlive the request. ## Cloudflare Bindings On Cloudflare Workers, import bindings from `cloudflare:workers`. Use Waku request context for request data, and Cloudflare's runtime APIs for environment bindings, D1, KV, and `waitUntil`. ```tsx import { env } from 'cloudflare:workers'; // eslint-disable-line import/no-unresolved import { getRequest } from 'waku/router/server'; export default async function Page() { const country = getRequest().headers.get('cf-ipcountry'); const store = country ? await env.DB.prepare('SELECT * FROM store WHERE country = ?') .bind(country) .first() : null; return
{JSON.stringify(store, null, 2)}
; } export const getConfig = async () => ({ render: 'dynamic' }) as const; ``` See [Run Waku on Cloudflare](/guides/cloudflare) for the full Cloudflare setup.