Create a new HonoX project with starter template
mainUse the hono-create command to scaffold a new project. When prompted, select the x-basic option to use the basic starter template.
npm create hono@latestrepository·main·Indexed 25 days ago
https://github.com/honojs/honoxA fast, lightweight meta-framework built on Hono and Vite for creating full-stack websites. HonoX features file-based routing, SSR, and Islands hydration. It supports custom renderers (including React, Preact, and Solid), nested layouts, and integration with Tailwind CSS and MDX.
Use the hono-create command to scaffold a new project. When prompted, select the x-basic option to use the basic starter template.
npm create hono@latestSince HonoX is Vite-centric, use the official Tailwind Vite instructions.
CSS Setup: In app/style.css, explicitly set the base path for source detection:
@import 'tailwindcss' source('../app');
Renderer Integration: Import the CSS file in your renderer using the <Link /> component from honox/server.
Vite Configuration: Add @tailwindcss/vite to your plugins and ensure the CSS file is included in the honox client input list.
// vite.config.ts
import honox from 'honox/vite'
import { defineConfig } from 'vite'
import build from '@hono/vite-build/cloudflare-workers'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
honox({
client: {
input: [
'/app/client.ts',
'/app/style.css',
],
},
}),
build(),
tailwindcss(),
],
})If using secureHeaders middleware, you can retrieve the nonce via c.get('secureHeadersNonce') and pass it to the <Script /> component.
// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'
import { Script } from 'honox/server'
export default jsxRenderer(({ children }, c) => {
return (
<html lang='en'>
<head>
<Script src='/app/client.ts' async nonce={c.get('secureHeadersNonce')} />
</head>
<body>{children}</body>
</html>
)
})Use @hono/vite-ssg to generate static HTML for each route.
To include client-side scripts and assets in SSG build:
honox/vite/client in the client mode plugin list.client and default modes:
vite build --mode client && vite build// vite.config.ts
import ssg from '@hono/vite-ssg'
import honox from 'honox/vite'
import client from 'honox/vite/client'
import { defineConfig } from 'vite'
export default defineConfig(({ mode }) => {
if (mode === 'client') {
return {
plugins: [client()],
}
} else {
return {
build: {
emptyOutDir: false,
},
plugins: [honox(), ssg({ entry: './app/server.ts' })],
}
}
})By default, Vite optimizes process.env to an empty object during the build process, which prevents process.env.MY_VAR from working in built applications. To ensure environment variables are available at runtime, add define: { 'process.env': 'process.env' } to your vite.config.ts.
export default defineConfig({
define: {
'process.env': 'process.env', // <=== Add this line
},
plugins: [
honox({
devServer: { adapter },
client: { input: ['/app/client.ts', '/app/style.css'] },
}),
tailwindcss(),
build(),
],
})A server entry file is required at app/server.ts. Use createApp() from honox/server to initialize your application. This file is called by Vite during development or build phases.
// app/server.ts
import { createApp } from 'honox/server'
import { showRoutes } from 'hono/dev'
const app = createApp()
showRoutes(app)
export default appImplement a custom 404 handler by creating a _404.tsx file in your routes directory using the NotFoundHandler type from hono.
// app/routes/_404.tsx
import { NotFoundHandler } from 'hono'
const handler: NotFoundHandler = (c) => {
return c.render(<h1>Sorry, Not Found...</h1>)
}
export default handlerIntegration tests in HonoX use app.request() against a Hono instance created by createApp() to validate routing, middleware, and renderers. It is recommended to use Vitest for testing due to its close integration with Vite.
Setup:
npm install -D vitestnpx vitest runIf you use a vitest.config.ts file, use mergeConfig from vitest/config to combine it with your existing vite.config.ts to ensure settings are correctly inherited.
// tests/integration/index.test.ts
import { describe, expect, it } from 'vitest'
import { createApp } from 'honox/server'
const app = createApp()
describe('Top page', () => {
it("should return 'Hello, Hono!' when name query param is 'Hono'", async () => {
const res = await app.request('/?name=Hono')
const text = await res.text()
expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toMatch(/text\/html/)
expect(text).toMatch(/<h1[^>]*>\s*Hello, Hono!\s*<\/h1>/)
})
})// vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config'
import viteConfig from './vite.config'
export default mergeConfig(
viteConfig,
defineConfig({
// Your Vitest-specific configuration here
})
)If you have a dist/.vite/manifest.json exported, you can use the <Script /> component from honox/server in your renderer for easier asset management. Ensure your vite.config.ts is configured with manifest: true in the client build mode.
// app/routes/_renderer.tsx
import { reactRenderer } from '@hono/react-renderer'
import { Script } from 'honox/server'
export default reactRenderer(({ children, title }) => {
return (
<html lang='en'>
<head>
<meta charSet='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<Script src='/app/client.ts' async />
{title ? <title>{title}</title> : ''}
</head>
<body>{children}</body>
</html>
)
})HonoX allows you to use UI libraries like React, Preact, or Solid as your renderer. To use React, follow these steps:
Install dependencies:
npm i @hono/react-renderer react react-dom hono
npm i -D @types/react @types/react-domDefine Props in global.d.ts to ensure type safety for your renderer:
import '@hono/react-renderer'
declare module '@hono/react-renderer' {
interface Props {
title?: string
}
}Create the renderer in app/routes/_renderer.tsx using reactRenderer.
Configure Client Hydration in app/client.ts using createClient from honox/client to map hydrate and createElement to your chosen library.
Configure Vite in vite.config.ts to handle the client mode build (for assets) and the ssr mode (marking react and react-dom as external).
Update tsconfig.json to set the jsxImportSource to react.
// app/routes/_renderer.tsx
import { reactRenderer } from '@hono/react-renderer'
export default reactRenderer(({ children, title }) => {
return (
<html lang='en'>
<head>
<meta charSet='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
{import.meta.env.PROD ? (
<script type='module' src='/static/client.js'></script>
) : (
<script type='module' src='/app/client.ts'></script>
)}
{title ? <title>{title}</title> : ''}
</head>
<body>{children}</body>
</html>
)
})To enable client-side interactivity, use a renderer that loads a client entry file (e.g., app/client.ts) and uses the <Script /> component from honox/server.
Create app/client.ts and call createClient():
// app/client.ts
import { createClient } from 'honox/client'
createClient()Use the <Script /> component in your _renderer.tsx to load the client entry point:
// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'
import { Script } from 'honox/server'
export default jsxRenderer(({ children }) => {
return (
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<Script src='/app/client.ts' />
</head>
<body>{children}</body>
</html>
)
})To use MDX, add @mdx-js/rollup to your vite.config.ts plugins. Ensure you configure the jsxImportSource to hono/jsx within the MDX plugin options.
// vite.config.ts
import devServer from '@hono/vite-dev-server'
import mdx from '@mdx-js/rollup'
import honox from 'honox/vite'
import remarkFrontmatter from 'remark-frontmatter'
import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
import { defineConfig } from 'vite'
export default defineConfig(() => {
return {
plugins: [
honox(),
mdx({
jsxImportSource: 'hono/jsx',
remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter],
}),
],
}
})