Project Structure
hono-preact/ # monorepo root
├── apps/
│ └── app/ # the deployed application
│ ├── src/
│ │ ├── server.tsx # Hono app — API routes + catch-all SSR handler
│ │ ├── client.tsx # Browser entry — hydrates the Preact app
│ │ ├── iso.tsx # Shared component tree — Router + route registration
│ │ ├── pages/ # Page components (one file per route)
│ │ │ └── docs/ # MDX content pages (auto-discovered as /docs/* routes)
│ │ ├── server/ # Server-only app code
│ │ │ └── layout.tsx # HTML shell rendered on every request
│ │ ├── components/ # Shared UI components
│ │ ├── styles/ # Global CSS
│ │ └── shims/ # Browser environment shims (e.g. process)
│ ├── vite.config.ts # Two build configs: client bundle + Worker bundle
│ └── wrangler.jsonc # Cloudflare Workers deployment config
└── packages/
├── iso/ # @hono-preact/iso — isomorphic loader utilities
├── server/ # @hono-preact/server — HonoContext + server middleware
└── vite/ # @hono-preact/vite — server-only Vite plugins
Key files
apps/app/src/server.tsx
The Hono application. Defines API routes and a catch-all GET * handler that SSR-renders the full Preact app via renderPage from @hono-preact/server and returns the HTML response.
app
.get('/api/movies', async (c) => { ... })
.use(location)
.get('*', (c) =>
renderPage(c, <Layout context={c} />, { defaultTitle: 'hono-preact' })
);
apps/app/src/client.tsx
The browser entry point. Picks up the server-rendered HTML and hydrates it in place. Runs only in the browser.
hydrate(<App />, document.getElementById('app'));
apps/app/src/iso.tsx
The shared component tree used by both the server (via Layout) and the client (via hydration). Defines the <Router> with all routes, including lazy-loaded standard pages and auto-discovered MDX pages.
const mdxModules = import.meta.glob('./pages/docs/*.mdx');
// Each .mdx file becomes a lazy /docs/* route — no manual registration needed.
apps/app/src/server/layout.tsx
Renders the full HTML shell: <head>, <body>, the #app mount point, and the <script> tag that loads the client bundle. The catch-all route in server.tsx renders this component and wraps its output in <!doctype html>.
@hono-preact/iso (workspace package)
Isomorphic utilities shared between server and client (packages/iso/):
loader.tsx—getLoaderDataHOC,useReloadhook, andLoader<T>typepage.tsx—Pagecomponent: runs serverLoader on the server, RPC stub in the browsercache.ts— single-value in-memory cache (createCache) to avoid re-fetching on client-side navigationguard.ts—createGuard,runGuards, guard typespreload.ts— reads loader data serialized intodata-loaderattributes on first hydration
@hono-preact/server (workspace package)
Server-only utilities (packages/server/):
context.ts—HonoContextanduseHonoContextfor accessing the HonoContextfrom Preact components during SSRmiddleware/location.ts—locationmiddleware that attaches the request URL to the Hono context for preact-iso'sLocationProvider
@hono-preact/vite (workspace package)
Vite plugins for the framework (packages/vite/). The primary export is honoPreact(), which bundles all framework Vite configuration into a single plugin. See Vite Configuration for usage.
honoPreact()— configures resolve deduplication, SSR bundling, client/server build outputs,@hono/vite-build, and@hono/vite-dev-serverserverOnlyPlugin— during the client bundle build, replaces any*.server.*import with a no-op stub so server code never reaches the browserserverLoaderValidationPlugin— fails the build if a.server.*file has named exports other thanserverGuards; the server loader must be the default export