Deploying
Node
npm run build # vite build --mode client && vite build
npm start # NODE_ENV=production node dist/index.jsThe build writes the server to dist/index.js and the browser's files to dist/client; the dependencies stay in
node_modules, so deploy them with it. NODE_ENV=production makes React use its production build. PORT sets the
port (3000 by default). On SIGTERM or SIGINT the server stops taking requests and waits for the running ones, up to
10 seconds.
Bun
The same build, with fondu({ target: 'bun' }); then NODE_ENV=production bun dist/index.js. Bun serves the app
itself, on PORT too.
Cloudflare Workers
npm run deploy # vite build && wrangler deploywrangler.jsonc's main is ./app/server.ts, the Worker. One vite build writes the browser's files and the
Worker, minified as Workers start faster with less code to parse (build.minify: false in the Worker's Vite
environment keeps it readable). wrangler deploy ships both: the files are served by Workers Static Assets.
Bindings (D1, KV, R2…) are declared in wrangler.jsonc and read from c.env in handlers; npm run cf-typegen writes
their types. With static pages, the pages chosen are files too.
The Content Security Policy
Fondu's policy allows only the site's own scripts. A script that a host adds to every page is blocked until the policy allows it, such as Cloudflare Web Analytics when the zone turns it on:
// app/server.ts
import { NONCE } from 'hono/secure-headers'
createApp({
secureHeaders: {
contentSecurityPolicy: {
scriptSrc: ["'self'", NONCE, 'https://static.cloudflareinsights.com'],
connectSrc: ["'self'", 'https://cloudflareinsights.com'],
},
},
})A directive given replaces Fondu's one, so keep 'self' and the nonce in scriptSrc.
Caching
The built assets in /assets/ have hashed names, so they are served with a year-long Cache-Control and
immutable: by the Node and Bun server, and on Cloudflare through a generated _headers (unless public/_headers
exists). Pages are not cached: Cache-Control: private, no-cache.
A new version
Each build has a version. A visit from a tab opened before a deploy gets a full page load instead of the page, so the browser runs the new assets: Inertia's asset versioning, done for you.