import { pageMeta } from "../../meta" import { CodeBlock } from "../../highlight" // Pure content page — no React interactivity (TOC/copy/search are the layout enhancer + // the Nira island), so ship zero framework JS and avoid hydrating the inline-script DOM. export const hydrate = false export const meta = pageMeta( "Nifra — Deployment", "One Nifra app, one command per target — Bun, Node (Docker), Deno Deploy, Cloudflare Pages, Vercel Edge.", ) const SCAFFOLD = `# Scaffold the multi-target site with a chosen default deploy target: bun create nifra my-app --deploy vercel # or: bun | node | deno | cf-pages # add --framework to pick the UI too (react default): bun create nifra my-app --framework svelte --deploy vercel cd my-app && bun install bun run dev # local preview bun run build # builds for the chosen target bun run deploy # runs that target's deploy CLI (you stay logged-in to the vendor) # Or add --ci github to also emit a deploy-on-push GitHub Actions workflow: bun create nifra my-app --deploy cf-pages --ci github # → .github/workflows/deploy.yml (builds on every push/PR, deploys on push to main)` const CF = `// _worker.ts — the edge SSR entry import { toFetchHandler } from "@nifrajs/core" import { createWebApp } from "@nifrajs/web" import { reactAdapter } from "@nifrajs/web-react" import { clientEntry, manifest } from "./server-manifest" // generated by buildServer const app = createWebApp({ adapter: reactAdapter, manifest, clientEntry }) export default toFetchHandler(app) // a Workers/Pages fetch handler // build.ts — buildClient (→ /assets) + buildServer (edge conditions → _worker.js) import { buildClient, buildServer } from "@nifrajs/web/build"` export default function Deployment() { return (

Deployment

app.fetch(request) is a pure Web-standard handler, so the same app runs anywhere. Pick a runtime; the code doesn't change.

One command per target

create-nifra's site template ships every target's build + server entry and config. Pass --deploy <target> to make one the default — it points{" "} build/deploy at that target and fills in your project name. The per-target build:*/deploy:* scripts stay, so you can switch any time.

target build → deploy config scaffolded
Bun (flagship) dist-bun/ bun run start (any host)
Node dist-node/ docker build … && docker run Dockerfile + .dockerignore
Deno Deploy dist-deno/ deployctl deploy deno.json
Cloudflare Pages dist/ wrangler pages deploy wrangler.toml
Vercel Edge .vercel/output/ vercel deploy --prebuilt Build Output API v3

Nifra never runs the deploy or enters your cloud credentials — it scaffolds the config + a{" "} deploy script that shells out to the vendor CLI you've already authed.

CI: deploy on push

Add --ci github to emit a .github/workflows/deploy.yml tuned to the chosen target — it builds on every push/PR and deploys on a push to main.{" "} cf-pages uses cloudflare/wrangler-action, vercel the prebuilt{" "} vercel deploy, deno deployctl (OIDC). The workflow's header comment lists the exact repo secrets to set (e.g. CLOUDFLARE_API_TOKEN,{" "} VERCEL_TOKEN). Self-hosted bun/node have no universal push-to-deploy, so their workflow builds + uploads the bundle as an artifact and leaves a clearly-marked, host-specific deploy step for you to fill in (Fly, a registry push, SSH, …).

Bun (its home)

app.listen(3000) — the native Bun server. Sits at the raw Bun.serve{" "} ceiling (see benchmarks).

Every core: Bun is single-threaded per process — for multi-core boxes, spawn one process per core, each binding the same port with{" "} app.listen(PORT, {"{ reusePort: true }"}); the kernel load-balances connections across them (Linux balances ~evenly). A supervisor that spawns + restarts workers is ~20 lines — see examples/cluster.ts. Anything shared across workers (rate limits, sessions, pub/sub) needs a shared store, as in any multi-instance deploy.

Node & Deno

Self-hosting (no CDN in front)? Hand @nifrajs/node's serve a{" "} static mount and it serves the client bundle from disk — traversal-guarded, content-typed, with an immutable cache — before the app runs, leaving the SSR fast path untouched: serve(app, {'{ port, static: { dir: new URL("./assets/", import.meta.url) } }'}). On Cloudflare/Vercel the platform serves assets, so you don't need it there.

Cloudflare Workers / Pages (the edge)

buildServer bundles with edge conditions (workerd,{" "} edge-light) into a _worker.js; toFetchHandler(app) is the handler. For Pages, a _routes.json serves the client bundle statically. SSR verified on real workerd — this very site runs there.

Deploy: wrangler pages deploy dist (Pages) or wrangler deploy{" "} (Workers). Deno Deploy and Vercel Edge reuse the same build with their conditions.

) }