Configure the app
One optional file describes how the app answers HTTP, and the rest of the settings stay in the plugin.
dusk.config.ts holds what describes how the app answers HTTP: redirects, rewrites, response headers, route rules, security, environment variables, cache lifetimes and the deployment id. The file is optional. The plugin options in vite.config.ts cover the build itself: the source and output folders, the adapter and prerendering.
import { defineConfig } from "@shadow-js/dusk/vite";
export default defineConfig({
security: { allowedHosts: ["app.example"], trustedOrigins: ["https://partner.example"] },
env: { DATABASE_URL: { type: "url", required: true, secret: true } },
redirects: [{ source: "/old/:slug", destination: "/posts/:slug", permanent: true }],
rewrites: { beforeFiles: [{ source: "/legacy/*rest", destination: "/archive/*rest" }] },
headers: [{ source: "/assets/**", headers: { "cache-control": "public, max-age=31536000, immutable" } }],
routeRules: {
"/about": { prerender: true },
"/blog/**": { revalidate: 300 },
"/admin/**": { headers: { "x-robots-tag": "noindex" } },
},
cacheLife: { catalog: { stale: 60, revalidate: 600, expire: 3600 } },
deploymentId: "2026.10.09-1",
});
Every key is optional. The ones you reach for first:
| Property | Shape | Default | Purpose |
|---|---|---|---|
security |
Object | Handler defaults | Allowed hosts, trusted origins, headers, a Content-Security-Policy, CSRF, limits. |
env |
Variable schema | None | Variables the server checks when it starts: a missing one stops the app with a message that names it. |
redirects, rewrites, headers |
Routing rules | None | Declarative routing, matched before your routes. |
routeRules |
Path-to-rule record | None | Per-path settings: prerender a page, give it a cache lifetime, add headers. |
cacheLife |
Named lifetime record | Built-in profiles | Named lifetimes (stale, revalidate, expire, in seconds) for cached pages. |
deploymentId |
String | Build-generated ID | The id the browser sends with every server function call, so a call from an old tab is refused after a deploy. |
Rules#
Secrets never go in this file. The build writes the server part of it into the server bundle, so security.cookieSecrets is refused and a function anywhere in the file is a build error. Read secrets from the environment and pass them to createHandler in src/entry-server.ts.
An unknown key fails the build with a suggestion, and a bad rule names the file and the rule. The server checks the same values again when the bundle loads, so a bad value never serves a request. In dusk dev, changing the file restarts the dev server by itself.
When two places set the same option, the later one wins: the handler's defaults, then dusk.config.ts, then src/entry-server.ts. Plain objects merge key by key; arrays and other values replace.
Next: use a database. The full table of keys, precedence and the merge rules are in Configuration.