Deployment
Pick the path that matches your build. fuzor routes tells you whether the app needs a server.
Static builds
A static SPA or MPA build is a folder of files. Upload dist-spa/ or dist-mpa/ to any static host.
| Build | Host setting |
|---|---|
| SPA | Enable the SPA fallback so unknown paths serve index.html (for example Netlify and Cloudflare Pages do this for a root index.html; nginx needs try_files $uri /index.html). |
| MPA | None. Hosts such as Cloudflare Pages and Netlify serve the nearest 404.html and read _redirects. |
Check a build locally before uploading:
fuzor build --target mpa
fuzor preview --target mpa
Static hosts set their own caching headers. Serve assets/ as immutable and HTML with revalidation when your host lets you. MPA builds write a _headers file with revalidate cache rules, which Cloudflare and Netlify read.
Node server
Apps with api/ endpoints or server.ts data, and apps built with --server, produce dist-<target>-server/:
dist-mpa-server/
├── client/ # assets and the same prerendered documents as a static build
└── server/entry.mjs # exports manifest and handler
Run the generated handler with the Node adapter. The starter's server.mjs does this and only needs production dependencies:
pnpm install --prod
PORT=3000 HOST=0.0.0.0 node server.mjs mpa
Write your own with createNodeServer for more control; see the server API. fuzor preview --target mpa --host 0.0.0.0 uses the same adapter and also works, but it reads your Vite config, so it needs Vite installed. Both stop gracefully on SIGTERM. Put a reverse proxy or platform load balancer in front for TLS and compression.
Things to know:
- Restart after rebuilding. The server reads its manifest once at start.
- Memory is per process. In-memory state in endpoints (like the example guestbook) is lost on restart and not shared between instances; use a database.
- Errors go to the
onErroroption ofcreateNodeServerandcreateServerHandler; the default logs toconsole.error. Clients only see generic messages. - Body size. Requests over 1 MiB are rejected; change it with
maxBodyBytes.
CDN in front of a server
client/ contains the prerendered documents too, so a CDN or static host can serve every page while only dynamic requests reach Node:
| Path | Served by |
|---|---|
/api/* | Node server |
/_fuzor/data/* | Node server (only when pages have server.ts) |
| Everything else | Static files from client/ |
Keep the static host's SPA fallback or 404.html handling the same as for a static build. Documents served by the Node server carry ETags and honor revalidate metadata, so you can also cache them at the CDN instead of uploading client/.
Base paths
To deploy below a path, set Vite's base:
export default defineConfig({ base: "/shop/", plugins: [fuzorPlugin()] });
Links, the router, API endpoints (/shop/api/…), server data and assets all move below /shop/. Keep writing links as /about/ in pages; Fuzor adds the base at build time.
Publishing content changes
Run fuzor build again, for example from a scheduled CI job or a CMS webhook, then upload the new static folder or restart the server. A failed build leaves the previous output untouched, and pages that are already open keep working for one more deployment because the previous build's assets are carried forward. See build profiles.
Cloudflare Workers
Build with the Cloudflare adapter:
fuzor build --target mpa --adapter cloudflare
npx wrangler dev --config dist-mpa-server/wrangler.jsonc # run locally in the Workers runtime
npx wrangler deploy --config dist-mpa-server/wrangler.jsonc
Or build and upload in one step with fuzor deploy, which runs the Cloudflare build and then wrangler deploy with the generated config. --dry-run builds and validates the upload without publishing. Wrangler must be installed in the project (pnpm add -D wrangler) and logged in.
fuzor deploy --target mpa --adapter cloudflare --dry-run
fuzor deploy --target mpa --adapter cloudflare
The build adds cloudflare/worker.mjs and wrangler.jsonc to dist-<target>-server/. Workers static assets serve every prerendered page, _redirects, _headers and the SPA fallback or 404.html pages, so page views do not invoke the Worker. Only /api/* and /_fuzor/data/* run the Worker, which calls the same handler as the Node server. The config is regenerated on every build; copy it next to your project and point main and assets.directory at the build output to add bindings, routes or environments.
Things to know on Workers:
- Module-scope state lives per isolate and is not shared or durable; use KV, D1 or Durable Objects.
- The clock is fixed during module initialization; read
Date.now()inside handlers. - Pages served as static assets use
_headersforrevalidatecache rules instead of the handler's headers.
Other runtimes
Server builds export handler, a function from a Web Request to a Response, so any runtime with the Fetch API can serve them. Serve client/assets/ as static files and pass everything else to the handler.
Bun (verified with the examples/modes/mpa-server build: pages, API endpoints, 404 pages and assets):
// server.bun.mjs
import path from "node:path";
import { handler } from "./dist-mpa-server/server/entry.mjs";
const client = path.join(import.meta.dir, "dist-mpa-server/client");
Bun.serve({
port: Number(process.env.PORT ?? 3000),
async fetch(request) {
const { pathname } = new URL(request.url);
if (pathname.startsWith("/assets/")) {
const file = Bun.file(path.join(client, pathname));
if (await file.exists()) return new Response(file, { headers: { "cache-control": "public, max-age=31536000, immutable" } });
}
return handler(request);
}
});
Things to check before using another platform, none of which Fuzor verifies yet:
| Platform | Entry | Constraints |
|---|---|---|
| Deno and Deno Deploy | Deno.serve(handler) after serving client/ | Server data loaders and endpoints must avoid Node-only APIs unless the Node compatibility layer covers them; no writable file system on Deploy |
| Vercel | Build Output API: client/ as static files, a function for /api/* and /_fuzor/data/* | Edge functions only have Web APIs; Node functions have per-invocation time and body limits |
| Netlify | client/ as the publish directory (_redirects and _headers are already written), a Functions v2 handler (export default handler) for /api/* and /_fuzor/data/* | Function time and body limits |
| AWS Lambda | A response-streaming function URL or API Gateway with a Request/Response conversion | Needs an event conversion layer; cold starts load Effect and your modules |
On every serverless or edge platform, module-level state is per instance and not durable, and request bodies are limited by the platform as well as by Fuzor.
Not available yet
Built-in adapters for other platforms (Vercel, Netlify Functions, Deno Deploy) do not exist yet; see other runtimes for wiring the handler yourself. fuzor deploy supports Cloudflare only. The server handler uses Web Request and Response, so a platform adapter mostly needs to serve client/ and forward other requests to handler.