hono-preact

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.