Router@rhythmjs/router
Serving files
Static files are Bun's built-in routes: a Response for a known file, a directory route for a folder. Nothing to import; one rule to respect.
Known files#
A file you can name maps straight to a Response over Bun.file in Bun.serve's routes, served before your fetch ever runs:
Bun.serve({
routes: {
"/": new Response(Bun.file("public/index.html")),
"/favicon.svg": new Response(Bun.file("public/favicon.svg")),
},
fetch: toFetchHandler(app),
});Folders: directory routes#
A whole folder is one route whose path ends in /* and whose value is { dir }:
Bun.serve({
routes: {
"/static/*": { dir: "./public" },
},
fetch: toFetchHandler(app), // everything else is the app
});Bun handles, natively:
Content types#- Set from the file extension.
Revalidation#Last-Modifiedand a weakETag(W/"<size>-<mtime>") on every response;If-None-Match/If-Modified-Sinceanswer304 Not Modified.Range requests#Accept-Ranges: byteswithContent-Range; video seeking and resumable downloads work out of the box.Directories#- A trailing-slash request serves the folder's
index.html; without the slash Bun answers a301redirect that adds it. Safety#- The path after the prefix is percent-decoded once and opened relative to
dir; non-canonical paths (.., empty segments, encoded slashes) are rejected with404. statCache#- Per-path stat caching is on by default; pass
{ dir, statCache: false }to trade ~20 KB per route for always-fresh stats.
Warning: never mount at /*#
With "/*": { dir }, every URL that isn't a file dies in the directory route: your app's fetch never runs, and the whole application turns into file-not-found. Misses do not fall through.
Keep folders on dedicated prefixes (/static/*, /assets/*) and let fetch stay the app's. Root-level files (favicon, robots.txt) are explicit known-file routes, one line each. Route precedence - exact, then :param, then wildcard, then catch-all - means "/" and "/static/*" coexist cleanly.