---
name: dusk
description: "Build full-stack ShadowJS apps with Dusk: file routes, loaders, server functions, forms, streaming, middleware, security defaults and deploy adapters. Use it when a project imports @shadow-js/dusk, has src/routes, or when asked to add a page, an API route, a server function or a deploy target to a Dusk app."
---

# Dusk

Dusk is the full-stack framework for ShadowJS. An app is a Vite project with one plugin, `dusk()`, and a folder of route files. The path of a file below `src/routes` is its URL. A page can have a loader, which runs on the server for the first request and in the browser for every navigation after it. Server code lives in server functions: `async` functions the browser calls like local ones, which the build replaces with a stub that calls the handler.

Everything is served by one request handler, `fetch(Request) => Response`, with secure defaults (host checks, CSRF, secure headers, request limits). A deploy adapter (Node, Bun, Deno, Cloudflare, Vercel, Netlify, Tarout or a static export) wraps that handler for a platform.

## When to use

- The project depends on `@shadow-js/dusk` or has `dusk()` in `vite.config.ts`.
- You are asked to add or change a page, a layout, an API route, a server function, a form, a middleware or a deploy target.
- A build fails with an error that names a route file, a server function or an import chain that reaches server-only code.

## Install

```sh
npm create shadow-app@latest my-app -- --template dusk
cd my-app && npm run dev
```

## Project layout

`npm create shadow-app@latest my-app -- --template dusk` writes:

```text
.env.example
.env.public
.gitignore
README.md
dusk.config.ts
package.json
src/app.tsx
src/components/NotFound.tsx
src/entry-server.ts
src/index.css
src/routes/[...rest].tsx
src/routes/_layout.tsx
src/routes/about.tsx
src/routes/api/health.ts
src/routes/index.tsx
src/routes/posts/[id].tsx
src/routes/posts/index.tsx
src/server/db.server.ts
src/server/posts.ts
src/server/schema.ts
src/vite-env.d.ts
tsconfig.json
vite.config.ts
```

## API

### Server

#### `createHandler`

Creates the request handler: the single `fetch(Request) => Response` contract that adapters, the dev server, the test client and prerendering all call.

```ts
import { createHandler } from "@shadow-js/dusk/server";

function createHandler(options: HandlerOptions): DuskHandler
```

#### `serverFn`

Defines a server function.

```ts
import { serverFn } from "@shadow-js/dusk/server";

function serverFn<S extends StandardSchemaV1, Item>(
  options: StreamingServerFnOptions & { readonly input: S },
  handler: (
    input: InferOutput<S>,
    event: RequestEvent,
  ) => AsyncIterable<Item> | Promise<AsyncIterable<Item>>,
): ServerFn<InferInput<S>, AsyncIterable<Item>, InferOutput<S>>

function serverFn<Item>(
  options: StreamingServerFnOptions,
  handler: (
    input: unknown,
    event: RequestEvent,
  ) => AsyncIterable<Item> | Promise<AsyncIterable<Item>>,
): ServerFn<unknown, AsyncIterable<Item>, unknown>

function serverFn<S extends StandardSchemaV1, Output>(
  options: ServerFnOptions & { readonly input: S; readonly stream?: false },
  handler: (input: InferOutput<S>, event: RequestEvent) => Output | Promise<Output>,
): ServerFn<InferInput<S>, Awaited<Output>, InferOutput<S>>

function serverFn<Output>(
  options: ServerFnOptions & { readonly stream?: false },
  handler: (input: unknown, event: RequestEvent) => Output | Promise<Output>,
): ServerFn<unknown, Awaited<Output>, unknown>
```

#### `apiRoute`

Defines an API route module.

```ts
import { apiRoute } from "@shadow-js/dusk/server";

function apiRoute<const M extends ApiModule>(module: M): Readonly<M>
```

#### `getRequestEvent`

The event of the request being handled.

```ts
import { getRequestEvent } from "@shadow-js/dusk/server";

function getRequestEvent(): RequestEvent
```

#### `json`

A JSON response.

```ts
import { json } from "@shadow-js/dusk/server";

function json(data: unknown, init: ResponseInit = {}): Response
```

#### `defineEnv`

Describes the environment of the app.

```ts
import { defineEnv } from "@shadow-js/dusk/server";

function defineEnv<const S extends EnvShape>(
  shape: S,
  options: EnvOptions = {},
): EnvSchema<S>
```

#### `createSessionManager`

Creates a session manager: signed id cookie, server-side data, idle and absolute lifetimes, periodic id rotation, explicit `regenerate()` against fixation.

```ts
import { createSessionManager } from "@shadow-js/dusk/server";

function createSessionManager(options: SessionOptions): SessionManager
```

#### `createRateLimiter`

Creates a rate limiter.

```ts
import { createRateLimiter } from "@shadow-js/dusk/server";

function createRateLimiter(options: RateLimiterOptions): RateLimiter
```

### Browser

#### `Form`

A form.

```ts
import { Form } from "@shadow-js/dusk/client";

function Form(props: FormProps): unknown
```

#### `useSubmission`

The state of the latest submission of a server function (or of any submission when called without one).

```ts
import { useSubmission } from "@shadow-js/dusk/client";

function useSubmission<Input = unknown, Output = unknown>(
  target?: StubTarget,
): SubmissionView<Input, Output>
```

#### `useFetcher`

A fetcher: a mutation or a load that does not navigate and has its own state.

```ts
import { useFetcher } from "@shadow-js/dusk/client";

function useFetcher(key?: string): Fetcher
```

#### `Await`

Renders deferred data with a fallback and an error element.

```ts
import { Await } from "@shadow-js/dusk/client";

function Await<T>(props: AwaitProps<T>): unknown
```

#### `hydrateDusk`

Starts the app in the browser on top of the server's markup.

```ts
import { hydrateDusk } from "@shadow-js/dusk/client";

async function hydrateDusk(options: HydrateDuskOptions): Promise<DuskClient>
```

#### `useSSE`

Subscribes to a server-sent events route from a component: `data` holds the last event's data and `status` the connection's state.

```ts
import { useSSE } from "@shadow-js/dusk/client";

function useSSE<T = unknown>(url: string, options: UseSseOptions<T> = {}): StreamHandle<T>
```

#### `useWebSocket`

Connects a WebSocket route from a component: `data` holds the last message, `status` the connection's state, and `send` writes to it.

```ts
import { useWebSocket } from "@shadow-js/dusk/client";

function useWebSocket<T = unknown>(
  url: string,
  options: UseWebSocketOptions<T> = {},
): WebSocketHandle<T>
```

### Shared

#### `HttpError`

An error that carries an HTTP status.

```ts
import { HttpError } from "@shadow-js/dusk";

class HttpError extends Error
```

#### `redirect`

Creates the signal to throw for a redirect.

```ts
import { redirect } from "@shadow-js/dusk";

function redirect(to: string, options?: RedirectOptions): RedirectSignal

function redirect<P extends string>(
  target: RedirectTarget<P> & { readonly status?: RedirectStatus },
): RedirectSignal
```

#### `notFound`

Control flow: throw the result to report not-found.

```ts
import { notFound } from "@shadow-js/dusk";

function notFound(options?: {
  readonly routeId?: string;
}): NotFoundSignal
```

#### `isHttpError`

True for an {@link HttpError}, also across two copies of this package.

```ts
import { isHttpError } from "@shadow-js/dusk";

function isHttpError(value: unknown): value is HttpError
```

### Build and deploy

#### `dusk`

The Vite plugins of a Dusk app, in the order they must run: 1.

```ts
import { dusk } from "@shadow-js/dusk/vite";

function dusk(options: DuskOptions = {}): Plugin[]
```

#### `defineConfig`

Typed identity for `dusk.config.ts`: `export default defineConfig({ ...

```ts
import { defineConfig } from "@shadow-js/dusk/vite";

function defineConfig<const C extends DuskConfig>(config: C): C
```

### Node

#### `node`

The adapter for Node.js: writes `server.mjs`.

```ts
import { node } from "@shadow-js/dusk/node";

function node(options: NodeAdapterOptions = {}): Adapter
```

#### `serve`

Serves a handler over HTTP with Node's `http` module.

```ts
import { serve } from "@shadow-js/dusk/node";

async function serve(handler: Handler, options: ServeOptions = {}): Promise<ServerHandle>
```

## Conventions

- **A file is a route.** `src/routes/index.tsx` is `/`, `posts/[id].tsx` is `/posts/:id`, `[[lang]]` is optional, `[...rest]` is a splat of one segment or more, `(group)/` adds no segment, `_layout.tsx` wraps its folder, `_middleware.ts` runs before every route below it, and a name that starts with `-` or `.` is ignored.
- **A loader runs on both sides.** It runs on the server for the first request and in the browser afterward, so it must not touch a database, the file system or a secret. Call a server function, or fetch an API route.
- **Put server code in server functions.** Export `serverFn({ method, input }, handler)` from a file under `src/server/`. The browser build removes the handler and its imports. Give it an `input` schema (any Standard Schema: Zod, Valibot, ArkType); without one the handler receives `unknown`.
- **Name server-only modules `.server.ts`.** A `*.server.ts` file can only be imported by server code. Importing it from browser code is a build error that shows the import chain. The browser can read only `DUSK_PUBLIC_*` environment variables.
- **Use Form for mutations.** `<Form action={serverFnWithFormTrue}>` submits in place with JavaScript and posts like a plain form without it. Return `fail(400, { issues })` from the function to mark fields; read the outcome with `useSubmission`.
- **Steer with redirect and notFound.** `throw redirect('/login')` and `throw notFound()` from a loader, a middleware or a server function. Both are safe to import in the browser from `@shadow-js/dusk`.
- **Set the allowed hosts in production.** `security.allowedHosts` in `src/entry-server.ts` is the one option to always set. CSRF checks, secure headers and request limits are already on.
- **Pick the deploy target in one line.** `dusk({ adapter: node() })` in `vite.config.ts` (or `bun()`, `deno()`, `cloudflare()`, `vercel()`, `netlify()`, `tarout()`, `staticSite()`), then `npx dusk build`.

## Common mistakes

### Reaching for the database in a loader

The loader also runs in the browser, so the import of a database client ends up in the browser bundle or fails the build. Move the query into a server function and call that from the loader.

### Importing a `.server.ts` file from a component

The build stops with the import chain. Import it from a server function or a middleware instead, and give the component the function.

### Trusting the input of a server function

Anyone can post to `/_dusk/fn/<id>`. Validate with the `input` schema, and guard the function itself: a check in a layout decides what a page shows, not who may call the function.

### Changing data with GET

A `GET` server function is for reads and can be cached. A mutation is a `POST` (the default), which gets the CSRF check.

### A page and an API route on one path and method

The scanner refuses it with a message that names both files. Give the API route a path under `api/`.

## Documentation

### Start

- [Dusk](https://dusk.shadowjs.net/guides/dusk/): Build a full-stack ShadowJS app with file routes, loaders, server functions and a secure request handler, and learn what runs on the server and what in the browser.
### Learn

- [Routes are files](https://dusk.shadowjs.net/learn/dusk-routes/): A file in src/routes is a page, a folder is a URL segment, and brackets make a segment a param.
- [Load data](https://dusk.shadowjs.net/learn/dusk-loaders/): A loader runs on the server for the first request and on navigation after that, and the page reads its result with a hook.
- [Call the server](https://dusk.shadowjs.net/learn/dusk-server-functions/): A function that runs on the server and is called like any other function from the page.
- [Forms and mutations](https://dusk.shadowjs.net/learn/dusk-forms/): A form that posts to a server function works without JavaScript and gets better with it.
- [Middleware](https://dusk.shadowjs.net/learn/dusk-middleware/): A function that runs before the route and can answer, redirect or pass the request on.
- [Live data](https://dusk.shadowjs.net/learn/dusk-streaming/): Three ways to push data after the page loads, and the smallest one that fits.
- [Deploy your app](https://dusk.shadowjs.net/learn/dusk-deploy/): Pick an adapter, build, and run what it wrote.
- [Secure by default](https://dusk.shadowjs.net/learn/dusk-security/): Configure accepted hosts and understand the response when a request is refused.
- [Sessions](https://dusk.shadowjs.net/learn/dusk-sessions/): Keep who the user is on the server, and put only a signed id in a cookie.
- [Head and metadata](https://dusk.shadowjs.net/learn/dusk-metadata/): A route or layout exports metadata, and Dusk writes the head on the server and on every navigation.
- [Configure the app](https://dusk.shadowjs.net/learn/dusk-config/): One optional file describes how the app answers HTTP, and the rest of the settings stay in the plugin.
- [Use a database](https://dusk.shadowjs.net/learn/dusk-databases/): The connection lives in one server module, and callers get functions, never the client.
- [Sign users in](https://dusk.shadowjs.net/learn/dusk-auth/): Sign-in with any OpenID Connect or OAuth 2.0 provider, with sessions on the server and a guard on routes.
- [Images and islands](https://dusk.shadowjs.net/learn/dusk-components/): An image that keeps its box, and parts of a page that become interactive only when they need to.
- [Work offline](https://dusk.shadowjs.net/learn/dusk-pwa/): pwa() turns the client build into an installable app that survives a lost connection.
### Routes and data

- [Routing and data](https://dusk.shadowjs.net/guides/dusk-routing/): File routes, layouts and API routes, then loaders, the document head, redirects, errors and not-found, navigation and preloading.
- [Server functions](https://dusk.shadowjs.net/guides/dusk-server-functions/): Write functions that run on the server, call them from loaders and the browser, validate input, add middleware, and see what the build keeps out of client bundles.
- [Mutations and forms](https://dusk.shadowjs.net/guides/dusk-mutations/): Submit forms and server functions with one mutation model, read the outcome in the page, work without JavaScript and show deferred data with Await.
- [Real time](https://dusk.shadowjs.net/guides/dusk-realtime/): Choose between typed streaming server functions, server-sent events and WebSocket routes, and configure their auth, limits, heartbeats and scale-out.
- [Responses](https://dusk.shadowjs.net/guides/dusk-response/): useResponse sets the status, headers and cookies of a page's response from render, a loader or middleware, and what can still change after the first byte.
### Pages

- [Images, scripts and islands](https://dusk.shadowjs.net/guides/dusk-components/): Responsive images with an optimizer, scripts with loading strategies, a nonce and callbacks, and islands that hydrate server markup on a directive.
- [Metadata and head](https://dusk.shadowjs.net/guides/dusk-metadata/): Export metadata and viewport from routes and layouts, serve sitemap, robots and manifest, and draw social images.
### Ship it

- [Configuration](https://dusk.shadowjs.net/guides/dusk-config/): The declarative configuration of a Dusk app (routing rules, headers, security, env, caching, draft mode, instrumentation) and the order in which the rules apply.
- [Deploy](https://dusk.shadowjs.net/guides/dusk-deploy/): Build a Dusk app, run it with the Node server, put it in a container or behind a proxy, export it as static files, and wire up logging, health checks and graceful shutdown.
- [Security](https://dusk.shadowjs.net/guides/dusk-security/): What the request handler does with no configuration, how to configure headers, CSP, CSRF, cookies, sessions, rate limits and hosts, and a production checklist.
- [Authentication (OIDC)](https://dusk.shadowjs.net/guides/dusk-auth/): Sign users in with any OpenID Connect or OAuth 2.0 provider using the authorization code flow with PKCE, with ID token checks, refresh tokens, logout and a route guard.
- [Auth.js and better-auth](https://dusk.shadowjs.net/guides/dusk-auth-libraries/): Use Auth.js or better-auth in a Dusk app, mounted as an API route, with the same requireUser guard as the built-in OpenID Connect client.
- [Databases](https://dusk.shadowjs.net/guides/dusk-databases/): Connect a database from server functions and loaders, with examples for Drizzle on libSQL and Turso, for Supabase, and a Prisma setup.
- [Progressive web apps](https://dusk.shadowjs.net/guides/dusk-pwa/): pwa() writes the web app manifest, an offline page and a service worker from the build; caching rules per route, and what is never kept.
### Reference

- [Overview](https://dusk.shadowjs.net/reference/): Every exported function, type and class of the packages this site documents, from the TSDoc of their entry points.
- [@shadow-js/dusk](https://dusk.shadowjs.net/reference/dusk/): The 81 exports of @shadow-js/dusk: 21 functions, 2 classes, 35 interfaces, 21 types, 1 const and 1 component.
- [@shadow-js/dusk/server](https://dusk.shadowjs.net/reference/dusk-server/): The 298 exports of @shadow-js/dusk/server: 96 functions, 9 classes, 139 interfaces, 28 types, 25 consts and 1 component.
- [@shadow-js/dusk/client](https://dusk.shadowjs.net/reference/dusk-client/): The 55 exports of @shadow-js/dusk/client: 17 functions, 2 classes, 25 interfaces, 9 types and 2 components.
- [@shadow-js/dusk/node](https://dusk.shadowjs.net/reference/dusk-node/): The 19 exports of @shadow-js/dusk/node: 8 functions, 10 interfaces and 1 type.
- [@shadow-js/dusk/bun](https://dusk.shadowjs.net/reference/dusk-bun/): The 5 exports of @shadow-js/dusk/bun: 2 functions and 3 interfaces.
- [@shadow-js/dusk/deno](https://dusk.shadowjs.net/reference/dusk-deno/): The 5 exports of @shadow-js/dusk/deno: 2 functions and 3 interfaces.
- [@shadow-js/dusk/cloudflare](https://dusk.shadowjs.net/reference/dusk-cloudflare/): The 6 exports of @shadow-js/dusk/cloudflare: 5 functions and 1 interface.
- [@shadow-js/dusk/vercel](https://dusk.shadowjs.net/reference/dusk-vercel/): The 4 exports of @shadow-js/dusk/vercel: 3 functions and 1 interface.
- [@shadow-js/dusk/netlify](https://dusk.shadowjs.net/reference/dusk-netlify/): The 5 exports of @shadow-js/dusk/netlify: 3 functions and 2 interfaces.
- [@shadow-js/dusk/tarout](https://dusk.shadowjs.net/reference/dusk-tarout/): The 2 exports of @shadow-js/dusk/tarout: 1 function and 1 interface.
- [@shadow-js/dusk/static](https://dusk.shadowjs.net/reference/dusk-static/): The 12 exports of @shadow-js/dusk/static: 4 functions, 1 class, 6 interfaces and 1 const.
- [@shadow-js/dusk/vite](https://dusk.shadowjs.net/reference/dusk-vite/): The 93 exports of @shadow-js/dusk/vite: 33 functions, 2 classes, 48 interfaces, 6 types and 4 consts.

Every documentation page is also available as Markdown: add `index.md` to its address.

## Reference discovery

This site documents ShadowJS 1.0.0. The [complete page index](https://dusk.shadowjs.net/agent-index.json) includes every task, API symbol and Markdown URL. Read the relevant pages as needed; avoid loading the full catalog into context.
