Serving Static Files
Serve static assets, SPAs, and React applications using Thunder's built-in serveAssets utility.
Thunder is not limited to JSON APIs. It includes a built-in serveAssets utility that allows you to serve any static content - HTML pages, CSS, JavaScript bundles, images, and even fully built React or SPA applications - directly from your Thunder server.
Project Structure
Place your static files inside the public/ directory of your Thunder project:
Creating a Static Route
Scaffold the Route
Inside your router file, use the req-static snippet to generate a pre-configured static handler, or generate it manually using the req snippet and modify it as shown below.
Define the Wildcard Pattern
The route path uses a wildcard parameter to capture any sub-path beneath the mount point. This is powered by path-to-regexp:
import { Router } from "@/core/http/router.ts";
import { serveAssets } from "@/core/http/assets.ts";
import { paramsAsJson } from "@/core/http/utils.ts";
import { fromFileUrl } from "https://deno.land/std/path/mod.ts";
import z from "zod";
export default new Router("/", function staticFiles(router) {
router.get("{/*endpoint}", function index() {
const $params = z.object({
endpoint: z.array(z.string()).optional(),
});
return (req: Request) => {
const { endpoint } = $params.parse(paramsAsJson(req));
return serveAssets(
req,
fromFileUrl(import.meta.resolve("../public/www")),
endpoint?.join("/"),
);
};
});
});How It Works
| Parameter | Description |
|---|---|
req | The incoming Request object |
root | The absolute path to the directory containing your static files |
filePath | The relative file path derived from the URL, used to locate the specific asset |
The {/*endpoint} pattern captures everything after the base path as an array of path segments. For example:
| Request URL | endpoint value |
|---|---|
/ | undefined (serves index.html) |
/app.js | ["app.js"] |
/assets/logo.png | ["assets", "logo.png"] |
Serving a React Application
If you have a built React (or any SPA) application, place the build output inside public/www/ and use the same static route pattern. Thunder will serve index.html as the fallback for client-side routing:
return serveAssets(
req,
fromFileUrl(import.meta.resolve("../public/www")),
endpoint?.join("/"),
);This pattern works seamlessly with any framework that produces a static build output, including React, Vue, Svelte, and SolidJS.
Important Note on Direct Return
Unlike regular route handlers that return a shape/handler object, static handlers return a function directly:
// Regular API handler
return {
shape: () => ({ ... }),
handler: async (req) => { ... },
};
// Static file handler - returns a function directly
return (req: Request) => {
return serveAssets(...);
};Remember to use the @/ import alias when importing serveAssets and other framework utilities. Relative imports into core/ are forbidden.