File-Based Static Pages
Beyond APIs, BurgerAPI can serve static HTML files by leveraging the same intuitive file-based routing conventions.
Configuration
To enable this feature, simply tell BurgerAPI where your page files live using the pageDir option in the Burger constructor:
import { Burger } from "burger-api";
const burger = new Burger({
apiDir: "./src/api", // Don't forget your API directory!
pageDir: "./src/pages", // Directory containing page files
pagePrefix: "", // Optional: Prefix for page URLs (defaults to none)
// ... other options
});
burger.serve(4000);
Defining Pages
Organize your page files within the pageDir directory:
- Directory Structure = URL Path: Just like API routes, the folder structure maps directly to the URL path (relative to any
pagePrefix). - Supported Files: BurgerAPI looks for
.htmlfiles (served as HTML) and.tsxfiles (plain handler functions, see below). - URL Mapping Examples:
src/pages/index.htmlorsrc/pages/index.tsx→/src/pages/about.html→/aboutsrc/pages/user/profile.html→/user/profilesrc/pages/products/index.html→/products
- Grouping Folders (
(folder)): Use parentheses to organize files without affecting the URL (e.g.,src/pages/(marketing)/contact.html→/contact). - Dynamic Segments (
[param]): Capture URL segments using square brackets (e.g.,src/pages/posts/[slug].html).
Plain HTML Files (.html)
Good old .html files require no special handling. BurgerAPI serves them directly as static assets.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>About Us</title>
<link rel="stylesheet" href="/assets/css/style.css" />
</head>
<body>
<h1>About BurgerAPI</h1>
<p>Serving tasty web frameworks built on Bun!</p>
</body>
</html>
Dynamic Pages (.tsx)
A .tsx page is not React/JSX server-side rendering. It's a plain handler
function, exactly like an API route's GET handler, that returns a Response
directly. Use it when a page needs to compute its HTML from a route param,
a service, or anything else that a static .html file can't express.
import type { BurgerContext } from "burger-api";
export default async function GET(ctx: BurgerContext): Promise<Response> {
const slug = ctx.params.slug ?? "unknown";
return new Response(
`<!doctype html><html><body><h1>Post: ${slug}</h1></body></html>`,
{ headers: { "Content-Type": "text/html" } }
);
}
The default export receives the same BurgerContext an API route handler
does (ctx.params, ctx.services, etc.) and must return a Response;
returning a JSX element instead will not render anything. If you want actual
JSX templating, build the HTML string yourself (e.g. with a template
function or a JSX-to-string helper) and pass it to new Response(...); there
is no built-in SSR renderer in 1.0.
Hooks and context
Global and plugin onRequest hooks run for pages, assets, /openapi.json, and /docs, including in apps with no API routes. Global and plugin afterRoute / mapResponse hooks also run for these responses, so CORS or security headers are added there too.
Every .tsx page handler receives the same BurgerContext an API route handler does, so ctx.services, ctx.query, ctx.set and the Request surface all work. Static .html files are served as-is.
Methods
Pages, assets, /docs and /openapi.json answer GET and HEAD only. Every other method gets 405 Method Not Allowed with Allow: GET, HEAD and an RFC 9457 problem body.
Runtime support
serve() on Bun serves every page type. On WinterCG targets (toFetchHandler: Node, Cloudflare, Deno, Vercel), function page routes and embedded assets are served, but dynamic ([param]) pages are Bun-only. The framework logs one warning listing the page routes it cannot serve instead of failing silently.