BunSPA
Learn how to deploy Bun-based single-page applications to Cloudflare Workers using Alchemy.
Deploy a Bun-based SPA with an optional Cloudflare Worker backend. BunSPA uses Bun’s native bundler and dev server for the frontend, with Cloudflare Workers for the backend API.
Minimal Example
Section titled “Minimal Example”Deploy a basic Bun SPA with a single HTML entrypoint:
import { BunSPA } from "alchemy/cloudflare";
const app = await BunSPA("my-app", { frontend: "src/index.html",});
With Multiple HTML Files
Section titled “With Multiple HTML Files”Serve multiple pages by providing an array of HTML entrypoints:
import { BunSPA } from "alchemy/cloudflare";
const app = await BunSPA("my-app", { frontend: ["src/index.html", "src/about.html", "src/contact.html"],});
With Backend API
Section titled “With Backend API”Add a Cloudflare Worker backend to handle API requests:
import { BunSPA } from "alchemy/cloudflare";
const app = await BunSPA("my-app", { frontend: "src/index.html", entrypoint: "./src/worker.ts",});
Accessing the Backend from Frontend
Section titled “Accessing the Backend from Frontend”Use the getBackendUrl
utility to reliably get your backend URL in both development and production:
import { getBackendUrl } from "alchemy/cloudflare/bun-spa";
const apiBaseUrl = getBackendUrl();
// Make API requestsfetch(`${apiBaseUrl.protocol}${apiBaseUrl.host}/api/users`) .then(res => res.json()) .then(data => console.log(data));
For API routes with specific paths, pass the routePath
option:
const apiBaseUrl = getBackendUrl({ routePath: "/api" });
Hot Module Replacement
Section titled “Hot Module Replacement”Enable Hot Module Replacement (HMR) in your frontend code for instant updates during development. Add this to your main entry file:
import { StrictMode } from "react";import { createRoot } from "react-dom/client";import App from "./App.tsx";import "./index.css";
createRoot(document.getElementById("root")!).render( <StrictMode> <App /> </StrictMode>,);
// Enable hot module replacementif (import.meta.hot) { import.meta.hot.accept();}
The import.meta.hot.accept()
call tells Bun that this module can be hot-replaced. When you save changes to your frontend files, Bun will automatically update the running application without a full page reload, preserving your application state.
With Custom Bindings
Section titled “With Custom Bindings”Add Cloudflare resource bindings and secrets:
import { BunSPA, KVNamespace, D1Database } from "alchemy/cloudflare";
const kv = await KVNamespace("kv", { title: "my-kv-store",});
const db = await D1Database("db", { name: "my-database",});
const app = await BunSPA("my-app", { frontend: "src/index.html", entrypoint: "./src/worker.ts", bindings: { KV: kv, DB: db, API_KEY: alchemy.secret(process.env.API_KEY), },});
With Custom Build Configuration
Section titled “With Custom Build Configuration”Customize the build output directory:
import { BunSPA } from "alchemy/cloudflare";
const app = await BunSPA("my-app", { frontend: "src/index.html", outDir: "build/client", build: "bun run test && bun build src/index.html --outdir build/client",});
With Transform Hook
Section titled “With Transform Hook”The transform hook allows you to customize the wrangler.json configuration. For example, adding a custom environment variable:
await BunSPA("my-app", { frontend: "src/index.html", wrangler: { transform: (spec) => ({ ...spec, vars: { ...spec.vars, CUSTOM_VAR: "value", }, }), },});
Configuration Notes
Section titled “Configuration Notes”bunfig.toml Requirement
Section titled “bunfig.toml Requirement”BunSPA requires a bunfig.toml
file in your project root to expose BUN_PUBLIC_*
environment variables during development:
[serve.static]env='BUN_PUBLIC_*'
This allows Bun to inline environment variables prefixed with BUN_PUBLIC_
into your frontend code.
Accessing the Backend
Section titled “Accessing the Backend”Use the getBackendUrl
utility function from alchemy/cloudflare/bun-spa
to get the backend URL. This function automatically handles both development and production environments:
import { getBackendUrl } from "alchemy/cloudflare/bun-spa";
const apiBaseUrl = getBackendUrl();fetch(new URL('api/endpoint', apiBaseUrl));
Under the hood, this uses the BUN_PUBLIC_BACKEND_URL
environment variable in development, which is automatically set by Alchemy, and falls back to the current origin in production.