Request APIs
Request context, redirects, environment helpers — what's safe in templates and what belongs server-side
This page covers the runtime APIs your route templates and $server/* modules use to read request data, set cookies, redirect, and access environment values. For lifecycle hooks (request / route / response / error), see Middleware.
Virtual modules in route <script> blocks
Route <script> blocks compile to both server-side render code and client-side hydration code. Anything imported into a top <script> has to make sense on both sides — that's why the framework exposes a deliberately narrow virtual-module surface for routes.
koze:request
Safe request-derived values you can use in a route's top <script>:
<script>
import { url, pathname, searchParams, params, slug, method } from 'koze:request';
const currentPath = pathname;
const tab = searchParams.get('tab');
const postId = params.id;
const postSlug = slug;
</script>
| Export | Type | Description |
|---|---|---|
url |
URL |
Full request URL |
pathname |
string |
URL pathname |
searchParams |
URLSearchParams |
Query parameters |
params |
Record<string, string> |
Route params (e.g. { id: '123' }) |
slug |
string | undefined |
First param value or params.slug |
method |
string |
HTTP method |
The compiler enforces this allow-list in route scripts. That's deliberate: anything not in this list (request, headers, locals, anything derived from auth state) cannot be safely serialized to the client.
koze:navigation
Route-script redirect:
<script>
import { redirect } from 'koze:navigation';
if (!user) {
redirect('/auth/signin');
}
</script>
redirect() throws a RedirectError that the framework catches and converts to a 303 response. Only meaningful server-side; calling it during client hydration is a no-op the framework strips at compile time. The same redirect() import also works inside src/server/*.ts.
Client navigation:
<script>
import { navigateTo } from 'koze:navigation';
function openSettings() {
navigateTo('/settings');
}
</script>
<button>Settings</button>
navigateTo(path, { replace }) is browser-only. For same-origin URLs it
fetches the HTML page, updates history.pushState() or
history.replaceState(), swaps the document, and dispatches
koze:navigation. If the fetch fails or does not return HTML,
Koze falls back to normal location.assign() /
location.replace() navigation.
To refetch the current route after a mutation without pushing a new
history entry, use refreshRoute():
<script>
import { refreshRoute } from 'koze:navigation';
async function saveDraft() {
await saveDraftRpc();
await refreshRoute();
}
</script>
refreshRoute() uses the same same-origin HTML navigation pipeline as
navigateTo(), but replaces the current history entry.
Server module helpers
Inside src/server/*.ts modules, prefer the server-capable koze:* modules:
import { request, url, headers, params, locals } from 'koze:request';
import { redirect } from 'koze:navigation';
import { cookies } from 'koze:cookies';
Use cloudflare:workers directly for env.
locals is the single request-scoped scratchpad server code reads after middleware writes to ctx.locals:
// src/middleware.ts
auth: {
async request(ctx, next) {
ctx.locals.user = await loadUser(ctx);
return next();
},
},
// src/server/items.ts
import { locals } from 'koze:request';
export async function listItems() {
const { user } = locals as App.Locals & { user: { id: string } | null };
if (!user) return [];
// …
}
Type App.Locals in src/app.d.ts to get autocomplete on locals:
declare global {
namespace App {
interface Locals {
user: { id: string; email: string } | null;
}
}
}
export {};
Cookies
Server modules can read and write cookies through koze:cookies:
import { cookies } from 'koze:cookies';
const theme = cookies.get('theme');
cookies.set('theme', 'dark', { path: '/', sameSite: 'Lax' });
cookies.delete('theme', { path: '/' });
Environment helpers
dev — compile-time environment flag
import { dev } from 'koze:environment';
- During
vite dev→devevaluates totrue - During
vite build→devevaluates tofalse
Because dev is compiled to a literal boolean, the unused branch in any if (dev) { … } block tree-shakes out of the production worker. Use it to bypass auth gates locally, log verbosely, or inline dev-only stubs:
import { dev } from 'koze:environment';
defineMiddleware({
auth: dev
? { async request(_ctx, next) { return next(); } } // dev bypass
: kyzenAuthMiddleware(authConfig),
});
dev is safe to import from:
- Route
<script>blocks src/server/*.tsmodulessrc/middleware.ts
Worker bindings (env)
For D1, KV, R2, Durable Object namespaces, secrets, and any other Cloudflare binding, read from cloudflare:workers:
import { env } from 'cloudflare:workers';
const turnstileSiteKey = env.TURNSTILE_SITE_KEY ?? '';
Treat binding access as server-only. Templates and client scripts should receive precomputed values — pass turnstileSiteKey as a prop or render it into the template, don't import env into a route's top <script> (the compiler will strip the import on the client side anyway, but doing it explicitly keeps the data flow obvious).
Read next
- Middleware — the request lifecycle, hooks, and execution order
- Routing — file-based routing, params, layouts
- Configuration — where each kind of config lives