A Val Town adapter for Hono's serveStatic middleware. Lets you use the
standard Hono serveStatic API — the same one used on Deno, Bun, Node, and
Cloudflare Workers — to serve static files from your val's project files.
Hono's built-in serveStatic middleware requires filesystem access
(Deno.open, node:fs, etc.), which Val Town doesn't provide. The existing
std/utils provides staticHTTPServer() and serveFile(), but these don't
integrate with Hono's middleware system.
This adapter bridges that gap — you get the full serveStatic API (root,
path, rewriteRequestPath, onFound, onNotFound, mimes, etc.) working
natively on Val Town.
import { Hono } from "npm:hono@4";
import { serveStatic } from "https://esm.town/v/nbbaier/vt-hono-serve-static/main.ts";
const app = new Hono();
// Serve files under /static/* from the project root
app.use("/static/*", serveStatic({ root: "./" }));
// Serve a specific file at a fixed path
app.use("/favicon.ico", serveStatic({ path: "./favicon.ico" }));
// Rewrite request paths (e.g., /assets/* → /frontend/*)
app.use(
"/assets/*",
serveStatic({
root: "./",
rewriteRequestPath: (path) => path.replace(/^\/assets/, "/frontend"),
}),
);
// Callbacks for cache headers or debugging
app.use(
"/static/*",
serveStatic({
root: "./",
onFound: (path, c) => {
c.header("Cache-Control", "public, max-age=3600");
},
onNotFound: (path, c) => {
console.log(`${path} not found, requested ${c.req.path}`);
},
}),
);
app.onError((err) => Promise.reject(err));
export default app.fetch;
All standard
Hono ServeStaticOptions
are supported:
| Option | Type | Description |
|---|---|---|
root | string | Root directory for file resolution (e.g., "./", "./frontend") |
path | string | Serve a specific file regardless of request path |
rewriteRequestPath | (path: string) => string | Transform the request path before file lookup |
mimes | Record<string, string> | Additional MIME type mappings |
onFound | (path: string, c: Context) => void | Called when a file is found |
onNotFound | (path: string, c: Context) => void | Called when a file is not found |
precompressed | boolean | Check for .br/.gz variants (no-op on Val Town) |
Plus one Val Town-specific option:
| Option | Type | Description |
|---|---|---|
metaImportUrl | string | Override import.meta.url for resolving files from a different val |
Rendering mermaid diagram...
This adapter provides three platform-specific hooks to Hono's base
serveStatic:
getContent(path) — Fetches file content from your val via esm.town
using std/utils. TypeScript/TSX/JSX files are automatically transpiled to
JavaScript.isDir(path) — Heuristic check (paths without a file extension are
treated as directories, triggering index.html fallback).join(...paths) — Simple URL-style path concatenation (no filesystem
needed).std/utils reads all files as UTF-8 text via res.text().
Use external URLs, inline SVG, or icon fonts for images. This matches the
current std/utils limitation.precompressed is a no-op. Val Town doesn't store .br/.gz file
variants, so the option is accepted but has no effect.isDir check uses a heuristic (no file
extension = directory). Extensionless files like LICENSE or Makefile would
be incorrectly treated as directories.