[]previous and next page

createHandler options

You rarely call createHandler yourself: the generated server entry does, with the options of src/entry-server.ts.

For agentsUpdated

You rarely call createHandler yourself: the generated server entry does, with the options of src/entry-server.ts. The ones you will reach for first:

Option Purpose
security.allowedHosts The host names the server answers for. Set it in production
security.csp, security.headers, security.csrf, security.limits, security.trustProxy, security.cookieSecrets The security settings
middleware (event, next) => Response functions around every request
env A defineEnv schema, validated at startup
mode "stream" (default), "async" or "string"
onError, onWarning Where unexpected errors and configuration warnings go (default: console.error / console.warn)
document { lang, rootId, head } of the generated HTML document
trailingSlash, trustedOrigins, healthPath, rpc, maxPayloadBytes Routing and limit details
features The features the handler has (pages(), rpc(), ...; see below). The generated entry passes the ones the app uses
runMiddlewareForStaticFiles true runs the global middleware for public/ and build files before they are sent (default false; see security)

Features: pay for what the handler uses#

A handler has only the request pipeline until you list the features it needs. Each feature is an object from its own subpath. The generated server entry lists the ones the app uses: routing() when dusk.config.ts declares redirects, rewrites, headers or route rules, isr() when a page or a route revalidates, draftMode() when the configuration sets draft, and the server functions, API routes and streams when the app has them. An entry file (src/entry-server.ts) can set any option, so an app with one gets every option feature. A handler you write yourself lists only what it serves:

server.tsts
import { apiRoutes } from "@shadow-js/dusk/features/api-routes";
import { createHandler, type DuskRuntimeManifest } from "@shadow-js/dusk/server";

declare const manifest: DuskRuntimeManifest; // the manifest the build writes

// An API only: no page rendering, server functions, cache, streams, images or draft mode is in the bundle.
export const handler = createHandler({ manifest, features: [apiRoutes()] });
Feature Adds Needs
pages() Page rendering, the document shell, the SPA shell (renderShell), the navigation gate nothing
rpc() Server functions (/_dusk/fn/), the batch endpoint nothing (single-flight answers need pages())
isr() The page cache: revalidate, revalidatePath, revalidateTag, the cache option pages()
dataCache() cacheData and cachedFetch nothing
apiRoutes() API routes (app/api) nothing
streams() SSE and WebSocket routes, Handler.upgrade nothing
images() The image optimizer at /_dusk/image nothing
draftMode() The draft endpoints and the draft cookie, and the draft option nothing
routing() The routing option: redirects, rewrites, headers and route rules, validated when the handler is created nothing

A handler that asks for something it does not have is refused when it is created, with the factory to add: a page route without pages(), a cache option without isr(), a routing option without routing(), and so on. An option's validation runs in its feature, so a handler without the feature does not load the validator. src/entry-server.ts can list its own features too; the list replaces the generated default.

pages() also needs the router features its routes' exports use. A route that exports loader needs loaderCache() from @shadow-js/router/loaders, head needs headManagement() from @shadow-js/router/head, error needs errorBoundaries() from @shadow-js/router/errors, matchFilters needs matchFilters() from @shadow-js/router/filters, validateSearch needs searchValidation() from @shadow-js/router/search, notFound needs notFoundUi() from @shadow-js/router/not-found, and pending needs pendingUi() from @shadow-js/router/pending. A generated entry (dusk build) reads those exports and passes the features for you. A handler you write lists them in pages({ routerFeatures }), and pages() refuses a route whose feature is missing, naming the route, the export and the factory to add:

server.tsts
import { headManagement } from "@shadow-js/router/head";
import { loaderCache } from "@shadow-js/router/loaders";
import { pages } from "@shadow-js/dusk/features/pages";
import { createHandler, type DuskRuntimeManifest } from "@shadow-js/dusk/server";

declare const manifest: DuskRuntimeManifest; // a manifest whose page routes export loader and head

export const handler = createHandler({
  manifest,
  features: [pages({ routerFeatures: [loaderCache(), headManagement()] })],
});