BundleRoute
Serving for a directory of built front-end files: hashed assets, an entry document, and per-file-class caching.
BundleRoute is the RouteVariant.bundle member of the RouteBase family. It serves any directory of servable files, deriving cache headers from what kind of file each one is — immutable for build-hashed assets, revalidated for the entry document, and a configurable fallback for everything else. Its distinguishing behaviour is the entry-document fallback: a path that resolves to no file is answered with the entry HTML instead of a 404, which is what makes a single-page app survive a hard refresh on a client route. That fallback is the main use, not the only one — StaticRoute remains the plainer choice when a bundle's caching and fallback behaviour is not wanted.
new BundleRoute("/*", "./dist");Contents
BundleRoute
class
class BundleRoute<E extends string = string>extends RouteBase<never, never, never, BundleRouteRes, E>Serves a directory of built files.
Register it on a wildcard endpoint so every path below it reaches the route. A request resolves in three steps: the endpoint prefix is stripped to a sub-path by BundleRoute.resolveSubPath, the sub-path is joined onto BundleRoute.dir by BundleRoute.resolveTargetPath, and BundleRoute.resolveFile serves the result if it exists. When it does not and the request is not for an HTML file, the entry document named by BundleRoute.indexHtmlPath is served instead.
Files are matched against BundleRoute.definition to pick their Cache-Control: the entry document, anything under BundleRoute.assetsDirPath, and everything else each get their own policy.
Paths that escape BundleRoute.dir are rejected by BundleRoute.isTraversalAttempt before the filesystem is touched, so .. segments cannot reach files outside the directory.
Type parameters
E— The literal endpoint type, carried so the endpoint stays narrowly typed at the call site.
BundleRoute.constructor()
constructor();
constructor(endpoint: E, dir: string, definition?: BundleRouteDefinition);
constructor(endpoint?: E, dir?: string, definition?: BundleRouteDefinition)Creates a bundle route for subclasses, which declare BundleRoute.endpoint and BundleRoute.dir as class fields and call RouteBase.register themselves.
Creates a bundle route and registers it on the nearest App.
Parameters
endpoint— The path to serve the directory under. Use a wildcard so nested paths reach the route.dir— Directory to serve from. Every resolved path is confined to it.definition— OptionalBundleRouteDefinitionoverriding the bundle layout and caching policy. Defaults toDEFAULT_DEFINITION.
BundleRoute.dir
dir!: stringDirectory the files are served from. Every path resolved by the route is confined to it.
BundleRoute.ignore
ignore: Array<string>;Sub-paths that are answered with the entry document rather than the file they name. A trailing * makes a pattern a prefix match; leading slashes are optional.
Useful when a client-side route collides with a real file in the directory, or to keep a build artefact from being reachable.
BundleRoute.definition
definition: BundleRouteDefinition;The bundle's layout and caching policy. Defaults to DEFAULT_DEFINITION.
BundleRoute.indexHtmlPath
get indexHtmlPath(): stringPath of the entry document within BundleRoute.dir.
Returns — The path from BundleRouteDefinition.indexHtml, or the DEFAULT_DEFINITION value when it declares none.
BundleRoute.assetsDirPath
get assetsDirPath(): stringPath of the hashed-assets directory within BundleRoute.dir.
Returns — The path from BundleRouteDefinition.assetsDir, or the DEFAULT_DEFINITION value when it declares none.
BundleRoute.getEndpoints()
getEndpoints(): Array<string>Lists every file this route can serve, as sub-paths relative to BundleRoute.dir.
Walks BundleRoute.dir recursively and returns each file's path with a leading /, matching what BundleRoute.resolveSubPath would produce for a request reaching that file. Useful for generating a sitemap or verifying what a deployed bundle actually contains.
Returns — The sub-paths of every file under BundleRoute.dir.
BundleRoute.onFileNotFound
onFileNotFound: (subPath: string) => MaybePromise<BundleRouteRes>;Decides what to serve when a request resolves to no readable file — an HTML file that is genuinely missing, a directory with no entry document, or a path rejected by BundleRoute.isTraversalAttempt.
Replace it to serve a custom 404 page or redirect instead of throwing.
Parameters
subPath— The request path with the endpoint prefix stripped, as the client asked for it.
Returns — The BundleRouteRes to send instead.
Throws — Exception with Status.NOT_FOUND by default.
BundleRoute.resolveFile()
protected resolveFile(targetPath: string): XFile | nullResolves a filesystem path to a readable XFile, applying the entry-document fallback.
A missing non-HTML path falls back to the entry document, which is what serves client-side routes. A missing HTML path does not fall back — asking for a specific document that does not exist is a real 404 rather than a client route.
Parameters
targetPath— Path produced by BundleRoute.resolveTargetPath.
Returns — The XFile to serve, or null when nothing readable was found, which sends the request to BundleRoute.onFileNotFound.
BundleRoute.resolveSubPath()
protected resolveSubPath(pathname: string): stringStrips the route's own prefix from a request pathname, leaving the path relative to BundleRoute.dir.
Both /* and * endpoint suffixes are handled, and a pathname that does not start with the prefix is returned untouched. The result is percent-decoded, since URL.pathname leaves escapes like %20 intact and filesystem paths need the literal characters.
Parameters
pathname— The pathname of the incoming request.
Returns — The bundle-relative sub-path, "" or / for the route root.
BundleRoute.resolveTargetPath()
protected resolveTargetPath(subPath: string): stringJoins a sub-path onto BundleRoute.dir to get the file to read.
The route root maps to the entry document, as does any sub-path matching BundleRoute.ignore. The result is not yet known to be safe — BundleRoute.isTraversalAttempt checks it before it is opened.
Parameters
subPath— The sub-path from BundleRoute.resolveSubPath.
Returns — The joined filesystem path.
BundleRoute.isTraversalAttempt()
protected isTraversalAttempt(targetPath: string): booleanReports whether a resolved path escapes BundleRoute.dir.
Both sides are fully resolved before comparison, so .. segments and encoded variants are normalised away rather than matched textually. The separator check keeps a sibling directory sharing the root's name prefix from passing.
Parameters
targetPath— Path from BundleRoute.resolveTargetPath.
Returns — true when the path lies outside the served directory, in which case the request goes to BundleRoute.onFileNotFound without the file being opened.
BundleRoute.resolveResponseData()
protected resolveResponseData(file: XFile, ): Tuple<ReadableStream | Uint8Array, Record<string, string>>Produces the response body and headers for a resolved file.
Cache-Control is chosen by matching the file against BundleRoute.definition: the entry document first, then anything under BundleRoute.assetsDirPath, then BundleRouteDefinition.fallback. No fallback means no header.
Non-HTML files are streamed and carry an inline Content-Disposition, so large assets are never buffered. HTML is read into memory instead, which lets it carry an exact Content-Length — the entry document is small and served constantly, so the length is worth more than the streaming.
Parameters
file— TheXFileresolved for the request.
Returns — A Tuple of the body and the headers to set on Res.headers.
BundleRoute.variant
override readonly variant: RouteVariantMarks this route as RouteVariant.bundle for App route compilation.
BundleRoute.method
override readonly method: MethodBundles answer Method.GET only.
BundleRoute.endpoint
override endpoint!: EThe path the directory is served under. Use a wildcard so nested paths reach the route; BundleRoute.resolveSubPath strips the wildcard suffix.
BundleRoute.config
override readonly config?: RouteConfig<never, never, never, BundleRouteRes>Bundles take no params, search or body, so no RouteConfig schemas apply.
BundleRoute.handler
override handler: ContextHandler<never, never, never, BundleRouteRes>Serves the file a request resolves to.
Resolves the sub-path and target path, rejects traversal attempts, reads the file, then sets the headers from BundleRoute.resolveResponseData on Context.res and returns the body. Anything that fails to resolve goes to BundleRoute.onFileNotFound.
Parameters
c— TheContextfor the request.
Returns — The file body — a stream for non-HTML, bytes for HTML — or whatever BundleRoute.onFileNotFound produced.