Node.js, Bun and Deno
Not every app has to run on Cloudflare. Set target in void.config.ts to build a server for Node.js, Bun or Deno. Run it on your own machine, in a container or with any host.
On this pageBuild your app for Node.js, Bun or Deno · Know what the build output contains · Check which features work and which behave differently off Cloudflare
{ "target": "node" }Get started
1. Configure the target
// void.config.ts
{ "target": "node" }2. Install the server dependency (Node.js only)
Node.js needs one extra dependency, @hono/node-server. Bun and Deno use their built-in HTTP servers.
Now you will install the dependencies.
npm install @hono/node-serverExample output: npm finishes with added N packages and no errors.
3. Develop
Now you will start the dev server.
npx vite devExample output: Vite prints a local URL such as http://localhost:5173/.
The dev server reloads routes and pages when you edit them.
4. Build and run
Now you will build the app and start the built server.
npx vite build
node dist/ssr/index.jsExample output: The build finishes and writes files to dist/. The server starts and prints the address it listens on.
Now you will build and start the app with Bun.
bunx vite build
bun dist/ssr/index.jsExample output: The build finishes and writes files to dist/. The server starts and prints the address it listens on.
Now you will build and start the app with Deno.
deno run -A npm:vite build
deno run -A dist/ssr/index.jsExample output: The build finishes and writes files to dist/. The server starts and prints the address it listens on.
Run these commands from your app folder. The server listens on the PORT env variable, or on 3000 by default.
Deploy dist/ssr and dist/client together.
Build output
The build produces two entry points:
dist/
ssr/
app.js ← Hono app with static asset middleware (default export)
index.js ← imports app.js and starts the HTTP server
client/ ← static assets (pages mode only)
.vite/manifest.json
assets/
...app.js: exports the Hono app. Use it to embed the app, test it or build a custom server.index.js: importsapp.jsand starts the server. Run this file in production.
vite preview also uses app.js to serve your built app locally.
Installed packages that depend on void are bundled into dist/ssr. Some of their dependencies use a native addon or read files next to their own code. Add each such dependency to your app's dependencies, at the version the package uses. The build then leaves it to Node.
What works
These SDK features work the same on every target:
- File-based routing (
routes/,middleware/) - Pages mode with SSR (React, Vue, Svelte, Solid)
- Typed fetch client (
void/client) - Custom headers (
routing.headers) - Redirects (
routing.redirects) - Static site generation
- Vite preview for testing production builds locally
What is different
Environment variables
Set server values in your host's environment. Read them with process.env in Node.js or Bun, or Deno.env.get() in Deno. The void/env schema and secret workflow need Cloudflare.
For browser code, use Vite's import.meta.env.VITE_* values. They are embedded at build time. Never put secrets in them.
No Cloudflare bindings
These imports are not available on other targets. They cause a compile-time error:
| Import | CF Feature |
|---|---|
void/db | D1 (SQL database) |
void/kv | KV (key-value storage) |
void/storage | R2 (blob storage) |
void/auth | SDK-managed Better Auth |
void/ai | Workers AI |
void/sandbox | Cloudflare Sandboxes |
void/env | Typed environment schema |
For a database or storage, use an external provider and connect with standard Node.js libraries.
WebSocket route files (*.ws.ts) need Cloudflare. You can still use the void/ws browser client with a WebSocket server hosted elsewhere.
No cron job runtime
You can still define cron jobs in crons/. They compile into the bundle, but nothing runs them. On Cloudflare, Workers Cron Triggers call the scheduled handler. On Node.js, use an external scheduler to call the exported handler. Examples are node-cron, systemd timers or your host's cron.
No inbound email
Inbound email handlers and sendEmail need Cloudflare. Use an external mail provider on other targets.
No prerendering
Edge prerendering needs Cloudflare. Build-time static generation is available on all targets.
No void deploy
void deploy only handles Cloudflare and SDK platform deploys. For Node.js, Bun or Deno, run vite build. Then deploy dist/ with your host's workflow.
Ignored config fields
On other targets, the SDK warns about these Cloudflare-only void.config.ts fields and ignores them:
inference.bindings: Cloudflare binding configurationremote: remote binding proxyworker: Cloudflare-specific configrouting.revalidate: edge caching, only on Cloudflare
If you enable auth through void/auth, void/client or auth.ts, the build fails on other targets. Use Better Auth directly for Node.js, Bun and Deno.
Programmatic usage
app.js exports the Hono app. You can import it into a custom server:
import app from './dist/ssr/app.js';
// Use with any Node.js HTTP framework
const response = await app.fetch(new Request('http://localhost/api/hello'));
console.log(await response.json()); // { message: "Hello from Node.js!" }Use this for tests, for embedding in Express or Fastify, or for serverless hosts that accept a fetch handler.