Skip to content
brought to you byVoidZero
Private Beta:Void is currently in Private Beta. We do not recommend using it for mission-critical production workloads yet. Please back up your data regularly.

Node.js, Bun, and Deno

Void apps default to Cloudflare Workers, but you can target Node.js, Bun, or Deno instead by setting target in void.json. This is useful when you want to deploy to a traditional server, run in a container, or use a runtime that isn't Cloudflare.

json
{ "target": "node" }

Supported Targets

TargetServerStatic Assets
"node"@hono/node-server@hono/node-server/serve-static
"bun"Bun.serve()hono/bun
"deno"Deno.serve()hono/deno

All three produce the same project structure and use the same Hono-based routing. The only difference is the HTTP server and static file middleware.

Getting Started

1. Configure the target

json
// void.json
{ "target": "node" }

2. Install the server dependency (Node.js only)

Node.js requires @hono/node-server as an additional dependency. Bun and Deno use their built-in HTTP servers.

bash
npm install @hono/node-server

3. Develop

bash
npx vite dev

The dev server runs your Hono routes in Node.js through Vite's SSR module loading. You get the same hot-reload experience as the Cloudflare target, but without workerd.

4. Build and run

bash
npx vite build
node dist/ssr/index.js

For Bun:

bash
bunx vite build
bun dist/ssr/index.js

For Deno:

bash
deno run -A npm:vite build
deno run -A dist/ssr/index.js

The server listens on PORT (env variable) or 3000 by default.

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)
    assets/
      ...
  • app.js: exports the Hono app instance. Use it for programmatic embedding, testing, or custom server setups.
  • index.js: imports app.js and starts the server. This is what you run in production.

vite preview also uses app.js to serve your built app locally.

What Works

These Void features work identically across all targets:

What's Different

No Cloudflare bindings

The following imports are not available with a non-CF target and produce a compile-time error:

ImportCF Feature
void/dbD1 (SQL database)
void/kvKV (key-value storage)
void/storageR2 (blob storage)
void/authVoid-managed Better Auth
void/aiWorkers AI
void/envCF env type augmentation
void/wsDurable Objects + WebSockets

If you need a database or storage, use an external provider and connect via standard Node.js libraries.

No cron job runtime

You can still define cron jobs in crons/ and they will compile into the bundle, but there is no built-in scheduler to invoke them. On Cloudflare, Workers Cron Triggers call the scheduled handler automatically. On Node.js, you'll need an external scheduler (e.g. node-cron, systemd timers, or your hosting platform's cron) to trigger the exported handler.

No prerendering

Prerendering is a platform feature that relies on Cloudflare's edge cache and the Void deploy pipeline. It is not available for non-CF targets.

No void deploy

The void deploy CLI deploys to the Void platform on Cloudflare. For non-CF targets, build with vite build and deploy the dist/ directory using your hosting provider's workflow, whether that is Docker, systemd, PM2, Fly.io, Railway, or any other Node.js host.

Ignored config fields

These void.json fields are silently ignored (with a warning) when using a non-CF target:

  • inference.bindings: Cloudflare binding configuration
  • remote: remote binding proxy
  • worker: Cloudflare-specific config
  • routing.revalidate: edge caching, only on Cloudflare

If you enable auth through void/auth, void/client, or auth.ts, Void fails the build for non-CF targets. Use Better Auth directly for Node/Bun/Deno deployments.

Example: API + Pages

A typical Node.js Void app with API routes and React pages:

my-app/
  pages/
    layout.tsx
    index.tsx
    index.server.ts
    about.tsx
    about.server.ts
  routes/
    api/
      hello.ts
  package.json
  vite.config.ts
  void.json
json
// void.json
{ "target": "node" }
ts
// vite.config.ts
import { defineConfig } from 'vite';
import { voidPlugin } from 'void';
import { voidReact } from '@void/react/plugin';

export default defineConfig({
  plugins: [voidPlugin(), voidReact()],
});
ts
// routes/api/hello.ts
import { defineHandler } from 'void';

export const GET = defineHandler((c) => {
  return c.json({ message: 'Hello from Node.js!' });
});
bash
# Development
npx vite dev

# Production
npx vite build
node dist/ssr/index.js

Programmatic Usage

Since app.js exports the Hono app, you can import it into a custom server:

ts
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!" }

This is useful for testing, embedding in Express/Fastify, or running in serverless environments that accept a fetch handler.