townie-testing
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Viewing readonly version of main branch: v26View latest version
serveFile() is a Val Town utility function that serves files from your project with proper content types and handling.
- Reads a file from your Val Town project
- Returns it as an HTTP response
- Automatically sets the correct
Content-Typeheader based on file extension - Handles file not found errors gracefully
import { serveFile } from "https://esm.town/v/std/utils/index.ts";
import { Hono } from "https://esm.sh/hono";
const app = new Hono();
// Serve a specific file
app.get("/", (c) => serveFile("/frontend/index.html", import.meta.url));
// Serve any file in the frontend directory
app.get("/frontend/*", (c) => serveFile(c.req.path, import.meta.url));
path(string) - The file path within your project (e.g., "/frontend/index.html")import.meta.url- Always pass this as the second parameter (tells the function where your project is located)
serveFile() automatically sets the correct Content-Type header:
| File Extension | Content-Type |
|---|---|
.html | text/html |
.css | text/css |
.js, .mjs | application/javascript |
.json | application/json |
.png | image/png |
.jpg, .jpeg | image/jpeg |
.svg | image/svg+xml |
.txt | text/plain |
.pdf | application/pdf |
| And many more... |
// Serve everything in frontend/ and shared/
app.get("/frontend/*", (c) => serveFile(c.req.path, import.meta.url));
app.get("/shared/*", (c) => serveFile(c.req.path, import.meta.url));
import { readFile } from "https://esm.town/v/std/utils/index.ts";
app.get("/", async (c) => {
// Read the HTML file manually to modify it
let html = await readFile("/frontend/index.html", import.meta.url);
// Inject initial data to avoid extra round-trips
const initialData = await fetchInitialData();
const dataScript = `<script>
window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
</script>`;
html = html.replace("</head>", `${dataScript}</head>`);
return c.html(html);
});
app.get("*", async (c) => {
// Try to serve the requested file first
try {
return await serveFile(c.req.path, import.meta.url);
} catch {
// If file doesn't exist, serve index.html (for client-side routing)
return serveFile("/frontend/index.html", import.meta.url);
}
});
app.get("/assets/*", async (c) => {
const response = await serveFile(c.req.path, import.meta.url);
// Add cache headers for static assets
response.headers.set("Cache-Control", "public, max-age=31536000");
return response;
});
When you call serveFile(c.req.path, import.meta.url):
- Path Resolution: Uses
import.meta.urlto locate your project - File Reading: Reads the file at the specified path
- Content-Type Detection: Analyzes file extension to set proper MIME type
- Response Creation: Returns a
Responseobject with the file contents and headers - Error Handling: Returns appropriate HTTP errors if file doesn't exist
- Always use
import.meta.urlas the second parameter - File paths start with
/(e.g., "/frontend/index.html", not "frontend/index.html") - Works with any file type that Val Town supports (text-based files)
- Automatic error handling - returns 404 if file doesn't exist
- No caching by default - add your own cache headers if needed
❌ DON'T use Hono's serveStatic middleware in Val Town:
// This doesn't work in Val Town
import { serveStatic } from 'https://esm.sh/hono/middleware';
✅ DO use Val Town's serveFile utility:
// This works perfectly in Val Town
import { serveFile } from "https://esm.town/v/std/utils/index.ts";
readFile()- Read file contents as a string (useful for modifying before serving)listFiles()- List all files in the project