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:

index.html
app.js
styles.css

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:

routes/static.ts
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

ParameterDescription
reqThe incoming Request object
rootThe absolute path to the directory containing your static files
filePathThe 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 URLendpoint 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:

routes/static.ts
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.

On this page