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)#
| Option | Type | Default | Description |
|---|---|---|---|
adapter | HonoPreactAdapter | required | Deployment target, e.g. cloudflareAdapter() or nodeAdapter(). |
layout | string | 'src/Layout.tsx' | Root layout component path. |
routes | string | 'src/routes.ts' | Route table path. |
api | string | 'src/api.ts' | Optional custom routes; loaded only if the file exists. |
appConfig | string | 'src/app-config.ts' | Optional app config; loaded only if the file exists. |
serverDir | string | 'src/server' | Registry folder for route-less server modules; globbed if it exists. See The src/server registry. |
clientEntry | string | 'virtual:hono-preact/client' | Client entry module id. |
css | { global?: string; autoSplit?: boolean; minSize?: number } | undefined | Framework-owned global stylesheet delivery and build-time auto-split tuning. See Styling for the full pipeline. |
What the plugin handles#
honoPreact() configures everything the framework requires:
| Concern | What it sets |
|---|---|
| Preact deduplication | resolve.dedupe for preact, preact/hooks, preact-iso |
| Build target | build.target: 'esnext', build.assetsDir: 'static' |
| Client build output | static/client.js entry, hashed chunk and asset filenames in the client environment |
| Deployment target | Delegated to the configured adapter (Cloudflare Workers, Node.js, etc.), which supplies its own Vite plugins and entry wrapper |
| Server-only imports | serverOnlyPlugin stubs static *.server.* imports and dynamic () => import('./*.server.*') calls in the client bundle (see Project Structure for the .server.* file convention) |
| Loader validation | serverLoaderValidationPlugin enforces .server.* export conventions |
| Module identity | moduleKeyPlugin injects a path-derived __moduleKey into each .server.* file for RPC routing |
| Guard strip | guardStripPlugin rewrites opposite-environment middleware bodies to no-ops so server-only code tree-shakes out of the client bundle |
| Browser shim | clientShimPlugin prepends a globalThis.process ??= ... shim to the client entry so libraries reading process.env.NODE_ENV at module-eval time do not throw |
| CSS pipeline | build.cssMinify: 'lightningcss' and Baseline browser targets (css.lightningcss.targets), unless you set cssMinify or css.lightningcss yourself, in which case your config wins outright. See Styling. |
Peer dependencies#
Peer deps depend on the adapter you pick. Install the ones that match your deployment target.
npm install -D @cloudflare/vite-plugin wranglernpm install @hono/node-serverAdd @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.
A custom entry takes over the virtual entry's responsibilities. Before hydrating, call bootClient() (exported from hono-preact). It installs the client runtime services the framework relies on: the history shim (back/forward direction tracking), the navigation transition scheduler (view transitions on route changes), and the stream registry (live-loader streams). Skipping the call silently disables all three, so the dev server warns when the configured clientEntry module never references bootClient.
// src/main.tsx
import { hydrate } from 'preact';
import { LocationProvider } from 'preact-iso';
import { Routes, bootClient } from 'hono-preact';
import routes from './routes.js';
bootClient();
hydrate(
<LocationProvider>
<Routes routes={routes} />
</LocationProvider>,
document.getElementById('app')
);