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

Vite Configuration#

The hono-preact/vite subpath exports a honoPreact() plugin that configures Vite for the framework's two-pass build and dev server. Your vite.config.ts only needs to wire in the plugins that are specific to your application.

Minimal setup#

import { honoPreact } from 'hono-preact/vite';
import { cloudflareAdapter } from 'hono-preact/adapter-cloudflare';
import { defineConfig } from 'vite';

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

adapter is required; honoPreact() throws a clear error if it is omitted. The plugin auto-includes @preact/preset-vite internally, so you don't import it yourself. The framework ships two adapters today: cloudflareAdapter() for Cloudflare Workers, imported from hono-preact/adapter-cloudflare, and nodeAdapter() for Node.js (via @hono/node-server), imported from hono-preact/adapter-node. Each adapter supplies its own Vite plugins and entry wrapper, so the framework core stays target-agnostic.

honoPreact(options)#

OptionTypeDefaultDescription
adapterHonoPreactAdapterrequiredDeployment target, e.g. cloudflareAdapter() or nodeAdapter().
layoutstring'src/Layout.tsx'Root layout component path.
routesstring'src/routes.ts'Route table path.
apistring'src/api.ts'Optional custom routes; loaded only if the file exists.
appConfigstring'src/app-config.ts'Optional app config; loaded only if the file exists.
clientEntrystring'virtual:hono-preact/client'Client entry module id.

What the plugin handles#

honoPreact() configures everything the framework requires:

ConcernWhat it sets
Preact deduplicationresolve.dedupe for preact, preact/compat, preact/hooks, preact-iso
Build targetbuild.target: 'esnext', build.assetsDir: 'static'
Client build outputstatic/client.js entry, hashed chunk and asset filenames in the client environment
Deployment targetDelegated to the configured adapter (Cloudflare Workers, Node.js, etc.), which supplies its own Vite plugins and entry wrapper
Server-only importsserverOnlyPlugin stubs static *.server.* imports and dynamic () => import('./*.server.*') calls in the client bundle (see Project Structure for the .server.* file convention)
Loader validationserverLoaderValidationPlugin enforces .server.* export conventions
Module identitymoduleKeyPlugin injects a path-derived __moduleKey into each .server.* file for RPC routing
Guard stripguardStripPlugin rewrites opposite-environment middleware bodies to no-ops so server-only code tree-shakes out of the client bundle
Browser shimclientShimPlugin prepends a globalThis.process ??= ... shim to the client entry so libraries reading process.env.NODE_ENV at module-eval time do not throw

Peer dependencies#

Peer deps depend on the adapter you pick. Install the ones that match your deployment target.

npm install -D @cloudflare/vite-plugin wrangler

Add @hono/node-ws to the Node.js install if you want WebSocket support.

Adding MDX#

MDX is a user-space choice and not included in the plugin. Add it alongside honoPreact():

import { honoPreact } from 'hono-preact/vite';
import { cloudflareAdapter } from 'hono-preact/adapter-cloudflare';
import mdx from '@mdx-js/rollup';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    honoPreact({ adapter: cloudflareAdapter() }),
    Object.assign(mdx({ jsxImportSource: 'preact' }), { enforce: 'pre' }),
  ],
});

The enforce: 'pre' placement ensures MDX files are transformed to JSX before other plugins see them.

Path aliases#

honoPreact() does not add path aliases; those belong in your config. A common setup:

import { resolve } from 'node:path';

export default defineConfig({
  resolve: {
    alias: [{ find: '@', replacement: resolve(__dirname, './src') }],
  },
  plugins: [honoPreact({ adapter: cloudflareAdapter() })],
});

Custom client entry path#

The plugin defaults to the framework-generated virtual module virtual:hono-preact/client, which hydrates <Routes> under <LocationProvider> for you. Override it only when you need a hand-written entry:

honoPreact({
  adapter: cloudflareAdapter(),
  clientEntry: 'src/main.tsx',
});

clientEntry is the single source of truth for both the rollup input and the clientShimPlugin transform target, so you only set it in one place.