> ## Documentation Index
> Fetch the complete documentation index at: https://better-router.bansal.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment

> Build once. Serve the fetch handler on Node, Bun, Deno, or Cloudflare Workers.

```bash theme={null}
npx vite build
```

| Output                 | Role                                               |
| ---------------------- | -------------------------------------------------- |
| `dist/client`          | JS/CSS for hydration, SPA, islands, plus `public/` |
| `dist/server/index.js` | `{ fetch, scheduled, queue, email }`               |

`package.json` scripts:

```json theme={null}
{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}
```

`vite preview` serves the **client** build only. It is not a production Node server. If you deploy to Node, add the `server.mjs` below after `vite build`.

## Cloudflare Workers

Follow [Cloudflare Workers](/cloudflare) and [Bindings](/bindings). Short version:

```bash theme={null}
npx vite build
npx wrangler deploy
```

```jsonc title="wrangler.jsonc" theme={null}
{
  "name": "my-app",
  "compatibility_date": "2026-09-01",
  "compatibility_flags": ["nodejs_compat"],
  "main": "./dist/server/index.js",
  "assets": { "directory": "./dist/client" }
}
```

Force the bundler target with `betterRouter({ target: "cloudflare" })`. Presence of `wrangler.jsonc` already selects Cloudflare.

## Node

Add a small production server and `"start": "node server.mjs"` when you deploy to Node:

```ts title="server.mjs" theme={null}
import { serve } from "@hono/node-server";
import { serveStatic } from "@hono/node-server/serve-static";
import { Hono } from "hono";
import worker from "./dist/server/index.js";

const app = new Hono();
app.use("/assets/*", serveStatic({ root: "./dist/client" }));
app.use("/favicon.ico", serveStatic({ root: "./dist/client" }));
app.all("*", (c) => worker.fetch(c.req.raw, process.env, {}));

serve({ fetch: app.fetch, port: Number(process.env.PORT ?? 3000) });
```

Load `.env` yourself in production (Docker, systemd, or `node --env-file=.env`). Vite will not inject it into `dist/server`.

D1, R2 bindings, and Cloudflare Queues do not exist on Node. Pass `process.env` as `env` so `getEnv()` still sees your secrets.

## Bun and Deno

The worker module is a `fetch` listener:

```ts theme={null}
import worker from "./dist/server/index.js";

Bun.serve({
  port: 3000,
  fetch: (request) => worker.fetch(request, Bun.env, {}),
});
```

Deno is the same idea with `Deno.serve` and `Deno.env`.

## What to verify after deploy

* `/` returns HTML with `#app` and a script at `/assets/client.js` (path may be hashed depending on Vite)
* `/api/hello` returns JSON
* Bindings you declared in Wrangler are readable from `getEnv()` on a Worker request

SPA client URLs must be served by `fetch` (the Worker or Node Hono app), not only a CDN that 404s unknown paths. Cloudflare `assets` handles static files and falls through to the Worker for the rest.
