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-server-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.