Skip to content

strblr/bun-spa

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bun-spa

Serve bundled SPAs (like a vite build) from a Bun server, fast and simple.

  • What it does: Loads your built files from dist/ (customizable) at startup, caches them in memory, and serves them. Unknown routes fall back to index.html (also customizable).
  • Why it’s fast: Everything is served directly from memory after the initial load. There are no disk reads during requests.
  • Why it’s cool: You can inject dynamic content (like meta tags for social sharing) into your SPA at request time. No need for heavy frameworks like Next.js.

Install

bun add bun-spa

Quick start

import { bunSpa } from "bun-spa";

const app = await bunSpa();

Bun.serve({
  routes: {
    "/*": app
  }
});

bunSpa returns a simple request handler, so it can be passed to the fetch option as well.

Inject dynamic content (optional)

Add a placeholder to your index.html (by default bun-spa looks for <!-- bun-spa-placeholder -->) and provide an indexInjector to replace it at request time. Useful for adding dynamic meta tags for social media previews.

IMPORTANT: If you inject user-provided content, make sure to sanitize it and follow strict guidelines to prevent security issues. See escape-goat, escape-html, sanitize-html, etc.

import { htmlEscape } from "escape-goat";

const app = await bunSpa({
  indexInjector: async ({ url }) =>
    `<meta property="og:description" content="${htmlEscape(
      await fetchDescription(url)
    )}">`
});

Dynamic headers (optional)

Provide a headers callback to set per-request headers. These merge with the default Content-Type the server sets based on the file.

const app = await bunSpa({
  headers: ({ file }) => ({
    "Cache-Control": file.isIndex
      ? "no-store"
      : "public, max-age=31536000, immutable"
  })
});

API

bunSpa(options?: BunSpaOptions): Promise<(req: Request) => Promise<Response>>

BunSpaOptions:

Option Type Default Description
dist string "./dist" Directory scanned at startup; files cached in memory.
glob string "**/*" Glob pattern for which files to load from dist/. Uses Bun.Glob syntax.
index string "index.html" SPA entry file served as fallback for unknown routes.
indexInjectorPlaceholder string | RegExp "<!-- bun-spa-placeholder -->" Marker in index.html to be replaced at request time.
indexInjector (options: BunSpaCallbackOptions) => string | Promise<string> undefined Returns a string that replaces the placeholder in index.html.
headers (options: BunSpaCallbackOptions) => Record<string, string> | Promise<Record<string, string>> undefined Additional headers to send with the response. Merged with default Content-Type.
disabled boolean false If true, the returned handler always responds with disabledResponse. Files aren't loaded.
disabledResponse Response new Response("bun-spa disabled", { status: 501 }) Response returned when disabled is true.

Other types:

interface BunSpaCallbackOptions {
  url: URL;
  req: Request;
  file: BunSpaFile;
}

interface BunSpaFile {
  type: string;
  content: ArrayBuffer;
  file: Bun.BunFile;
  isIndex: boolean;
}

Notes

  • Files are read once at startup and kept in memory for fast responses.
  • All unknown paths return index.html (with optional injection).
  • You are responsible for ensuring the security of any dynamically injected content; this library does not perform sanitization.
  • TypeScript types are included.

About

Serve single-page apps with Bun, fast and customizable

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published