hono-preact
Overview
Quick Start
The Route Table
Layouts & Nesting
Adding Pages
Active Links
Server Loaders
Loading States
Reloading Data
Prefetching
Streaming
Live Loaders
Realtime Channels
Server Actions
Validation
Optimistic UI
View Transitions
Middleware
CSRF Protection
CLI
Vite Config
Project Structure
Composing Hono Middleware
WebSockets
Rooms & Presence
renderPage
Link Prefetch
Build & Deploy
Overview
Dialog
Popover
Tooltip
Menu
Context Menu
Select
Combobox
Toast
renderElement
useControllableState
mergeRefs
useListNavigation
useTypeahead
useListboxSelection
usePosition
usePositioner
useDismiss
useFocusReturn
useSafeArea
usePresence

Build & Deploy#

hono-preact ships a build pipeline for each target runtime. Pick the Cloudflare adapter to run your app as a Worker with bindings and SSR in workerd; pick the Node adapter to deploy as a standard Node server. This page covers the dev, build, and deploy commands for both.

Adapters#

The framework targets whichever adapter you choose. Two adapters ship today: cloudflareAdapter() from hono-preact/adapter-cloudflare and nodeAdapter() from hono-preact/adapter-node. You select one in vite.config.ts by passing it to the framework plugin: honoPreact({ adapter: ... }). An adapter is required.

Dev, build, and deploy all behave per-adapter. The rest of this page covers both flows.

Cloudflare Workers#

This section assumes a cloudflareAdapter() in vite.config.ts:

import { cloudflareAdapter } from 'hono-preact/adapter-cloudflare';
// ...
plugins: [honoPreact({ adapter: cloudflareAdapter() })],

Development#

npm run dev

Starts the Vite dev server. The Cloudflare adapter runs your worker inside workerd (the real Cloudflare Workers runtime) via @cloudflare/vite-plugin, so development mirrors production: Cloudflare bindings, the SSR runtime, and WebSocket upgrades all behave the same as a deployed Worker. Hot module replacement works for both client components and server code.

Production build#

npm run build

A single vite build. Vite's Environment API builds the browser bundle and the Worker bundle together; there is no separate client pass. The serverOnlyPlugin still replaces every *.server.* import with a no-op stub so server code never enters the browser bundle.

Output is split into two directories so worker source can never be served as a static asset:

dist/
  client/                      # static assets, served from Cloudflare's CDN
    static/client.js            #   client entry
    static/<name>-<hash>.js     #   lazy route chunks
    static/<name>-<hash>.css
  hono_preact/                 # the Worker bundle
    index.js                    #   bundled Worker
    wrangler.json               #   generated deploy config

The Worker directory name is derived from the name field in wrangler.jsonc (hyphens become underscores), so hono-preact produces dist/hono_preact/.

Local preview#

npm run preview

Runs vite preview, which serves the production build through workerd again.

Configuring wrangler.jsonc#

wrangler.jsonc is the source of truth @cloudflare/vite-plugin reads:

{
  "name": "your-app-name", // also names the dist/<name>/ worker directory
  "main": "node_modules/.vite/hono-preact/server-entry.tsx",
  "compatibility_date": "2026-02-22",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "directory": "./dist/client",
  },
}

main points at the server entry the framework generates into the Vite cache directory; the framework writes that file before the build starts. assets points at the client build output. No run_worker_first list is needed: the client assets and the Worker bundle land in separate directories, so the Worker source is never exposed through the asset store. The build also writes a .assetsignore into dist/client/ that excludes the generated config from the asset upload.

Deploy#

npm run deploy

This runs wrangler deploy against the generated config in the Worker output directory (dist/hono_preact/wrangler.json), which references the built Worker and the dist/client/ assets. Run npm run build first so the output exists.

Node.js#

Setup#

Register the Node adapter in vite.config.ts:

import { honoPreact } from 'hono-preact/vite';
import { nodeAdapter } from 'hono-preact/adapter-node';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [honoPreact({ adapter: nodeAdapter() })],
});

@hono/node-server is a peer dependency, so install it alongside the framework: npm install @hono/node-server. @hono/node-ws is an optional peer if you need WebSocket upgrades.

Development#

npm run dev

Starts the Vite dev server. The Node adapter handles SSR through Vite's SSR module runner, so HMR works for both client and server code.

Production build and start#

vite build emits a server entry at dist/server/server-entry.js and the client bundle under dist/client/. Start production with:

node dist/server/server-entry.js

The server listens on PORT (default 3000).

Deploying#

Deploy the same way you would any Node server: a container image (Docker), a system service (systemd), or a PaaS that runs Node processes. Static assets must be served from dist/client/; the Node adapter serves them in-process when you run the bundled server, so a single node process handles both assets and SSR.

See also#

  • Vite Config: full honoPreact() options reference and peer-dependency setup.
  • Composing Hono Middleware: adding custom Hono routes and middleware alongside the framework (via src/api.ts).