rhythmjs

Search documentation

Search guides, the tutorial and every package.

On this page

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:

TypeScript
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 }:

TypeScript
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-Modified and a weak ETag (W/"<size>-<mtime>") on every response; If-None-Match / If-Modified-Since answer 304 Not Modified.
Range requests#
Accept-Ranges: bytes with Content-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 a 301 redirect 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 with 404.
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 /*#

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.