Request Context
Read request headers and pass request-scoped data to server components and server functions.
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.
The APIs in this guide currently use unstable_ names and may change.
Read the Current Request
Use unstable_getRequest in server-only code to access the current Request:
import { unstable_getRequest as getRequest } from 'waku/router/server';
const getCurrentPath = () => {
const req = getRequest();
return new URL(req.url).pathname;
};
export default function Page() {
const path = getCurrentPath();
return <p>Current path: {path}</p>;
}getRequest() throws if request context is not available. Call it during request handling, not in module scope.
unstable_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 unstable_getRequest().url can differ. Use the handler argument for routing-specific values and unstable_getRequest() for the original request.
unstable_getRequest and unstable_getHeaders are exported from waku/router/server and are available wherever the router runs your code: renders (request and build), API route handlers, and interceptors. 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 cached at build time, so do not read unstable_getRequest, headers, or interceptor-seeded state in a static page, layout, or slice — those values would be baked into the cached output. Read request-scoped data only in dynamic rendering. (During a static route build unstable_getRequest().url is the route's path, but a static slice build uses a placeholder root request that does not identify the slice.)
Read Request Headers
For headers, use unstable_getHeaders:
import { unstable_getHeaders as getHeaders } from 'waku/router/server';
export default function Page() {
const headers = getHeaders();
const userAgent = headers['user-agent'] || 'unknown';
return <p>User agent: {userAgent}</p>;
}The returned object is a snapshot of the request headers for the current request.
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 unstable_getRequest is available inside it.
Define your own store:
// src/lib/request-data.ts
import { AsyncLocalStorage } from 'node:async_hooks';
export type RequestData = {
country?: string;
};
const store = new AsyncLocalStorage<RequestData>();
export const getRequestData = (): RequestData => store.getStore() ?? {};
export const runWithRequestData = <T>(
data: RequestData,
fn: () => Promise<T>,
) => store.run(data, fn);In managed mode (no waku.server.tsx), seed the store from an interceptor in src/pages/_interceptors/request-data.ts:
import type { HandlerInterceptor } from 'waku/router/server';
import { unstable_getHeaders as getHeaders } from 'waku/router/server';
import { runWithRequestData } from '../../lib/request-data.js';
const requestDataInterceptor: HandlerInterceptor = (next) => {
const country = getHeaders()['cf-ipcountry'] ?? 'unknown';
return runWithRequestData({ country }, next);
};
export default requestDataInterceptor;With a custom waku.server.tsx using createPages, register the same logic through createInterceptor:
import { unstable_getHeaders as getHeaders } from 'waku/router/server';
import { runWithRequestData } from './lib/request-data.js';
createPages(async ({ createPage, createInterceptor }) => {
createInterceptor((next) => {
const country = getHeaders()['cf-ipcountry'] ?? 'unknown';
return runWithRequestData({ country }, next);
});
return [
// ...pages...
];
});Then read that data from server code:
import { getRequestData } from '../lib/request-data.js';
export default function Page() {
const { country } = getRequestData();
return <p>Country: {country ?? 'unknown'}</p>;
}If the value is just a request header, you can also read it from unstable_getHeaders() 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 unstable_getHeaders() in server code, and use middleware to set response cookies after rendering.
Read a request cookie from server code with the cookie package:
import * as cookie from 'cookie';
import { unstable_getHeaders as getHeaders } from 'waku/router/server';
export default function Page() {
const cookies = cookie.parse(getHeaders()['cookie'] ?? '');
return <p>Session: {cookies.sessionId ?? 'none'}</p>;
}Set a response cookie from middleware, which owns the response:
// src/middleware/session.ts
import * as cookie from 'cookie';
import type { MiddlewareHandler } from 'hono';
const sessionMiddleware = (): MiddlewareHandler => {
return async (c, next) => {
const cookies = cookie.parse(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.serialize('sessionId', 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.
import { env } from 'cloudflare:workers'; // eslint-disable-line import/no-unresolved
import { unstable_getRequest as getRequest } from 'waku/router/server';
export default async function Page() {
const req = getRequest();
const url = new URL(req.url);
const id = url.searchParams.get('id');
const item = id
? await env.DB.prepare('SELECT * FROM item WHERE id = ?').bind(id).first()
: null;
return <pre>{JSON.stringify(item, null, 2)}</pre>;
}See Run Waku on Cloudflare for the full Cloudflare setup.

