Let's create a basic HonoX application using hono/jsx as a renderer. This application has no client JavaScript and renders JSX on the server side.
Below is a typical project structure for a HonoX application.
.
├── app
│ ├── global.d.ts // global type definitions
│ ├── routes
│ │ ├── _404.tsx // not found page
│ │ ├── _error.tsx // error page
│ │ ├── x_renderer.tsx // renderer definition
│ │ ├── about
│ │ │ └── [name].tsx // matches `/about/:name`
│ │ └── _index.tsx // matches `/`
│ └── server.ts // server entry file
├── package.json
├── tsconfig.json
└── vite.config.ts
vite.config.tsThe minimum Vite setup for development is as follows:
import { defineConfig } from 'vite'
import honox from 'honox/vite'
export default defineConfig({
plugins: [honox()],
})
A server entry file is required. The file should be placed at app/server.ts. This file is first called by the Vite during the development or build phase.
In the entry file, simply initialize your app using the createApp() function. app will be an instance of Hono, so you can use Hono's middleware and the showRoutes() in hono/dev.
// app/server.ts
import { createApp } from 'honox/server'
import { showRoutes } from 'hono/dev'
const app = createApp()
showRoutes(app)
export default app
There are three ways to define routes.
createRoute()Each route should return an array of Handler | MiddlewareHandler. createRoute() is a helper function to return it. You can write a route for a GET request with default export.
// app/routes/_index.tsx
// `createRoute()` helps you create handlers
import { createRoute } from 'honox/factory'
export default createRoute((c) => {
return c.render(
<div>
<h1>Hello!</h1>
</div>
)
})
You can also handle methods other than GET by export POST, PUT, and DELETE.
// app/routes/_index.tsx
import { createRoute } from 'honox/factory'
import { getCookie, setCookie } from 'hono/cookie'
export const POST = createRoute(async (c) => {
const { name } = await c.req.parseBody<{ name: string }>()
setCookie(c, 'name', name)
return c.redirect('/')
})
export default createRoute((c) => {
const name = getCookie(c, 'name') ?? 'no name'
return c.render(
<div>
<h1>Hello, {name}!</h1>
<form method='POST'>
<input type='text' name='name' placeholder='name' />
<input type='submit' />
</form>
</div>
)
})
You can create API endpoints by exporting an instance of the Hono object.
// app/routes/about/index.ts
import { Hono } from 'hono'
const app = new Hono()
// matches `/about/:name`
app.get('/:name', (c) => {
const name = c.req.param('name')
return c.json({
'your name is': name,
})
})
export default app
Or simply, you can just return JSX.
// app/routes/_index.tsx
export default function Home(_c: Context) {
return <h1>Welcome!</h1>
}
Define your renderer - the middleware that does c.setRender() - by writing it in x_renderer.tsx.
Before writing x_renderer.tsx, write the Renderer type definition in global.d.ts.
// app/global.d.ts
import type {} from 'hono'
type Head = {
title?: string
}
declare module 'hono' {
interface ContextRenderer {
(content: string | Promise<string>, head?: Head): Response | Promise<Response>
}
}
The JSX Renderer middleware allows you to create a Renderer as follows:
// app/routes/x_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'
export default jsxRenderer(({ children, title }) => {
return (
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
{title ? <title>{title}</title> : <></>}
</head>
<body>{children}</body>
</html>
)
})
The x_renderer.tsx is applied under each directory, and the app/routes/posts/x_renderer.tsx is applied in app/routes/posts/*.
You can write a custom Not Found page in _404.tsx.
// app/routes/_404.tsx
import { NotFoundHandler } from 'hono'
const handler: NotFoundHandler = (c) => {
return c.render(<h1>Sorry, Not Found...</h1>)
}
export default handler
You can write a custom Error page in _error.tsx.
// app/routes/_error.tsx
import { ErrorHandler } from 'hono'
const handler: ErrorHandler = (e, c) => {
return c.render(<h1>Error! {e.message}</h1>)
}
export default handler