hono-inertia について
Hono、Inertia.js、hono/jsx を使い、フロントエンドを分けずに SPA のようなアプリを構築する TypeScript 開発者向けガイドです。ページプロパティの型生成や、Vite と Cloudflare Workers のデプロイ設定も扱います。
主な機能
- 01Hono から Inertia ページを返す
- 02hono/jsx によるクライアントページ作成
- 03ページプロパティ型の生成
- 04Vite によるバンドルと SSR
- 05Cloudflare Workers のデプロイ設定
活用シーン
- 01Hono アプリへのクライアント状態の追加
- 02楽観的 UI と複雑なフォームの構築
- 03SPA 用の別フロントエンドスタックの回避
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yusukebe/ skills --skill hono-inertiaインストール後、Claude Code で/hono-inertiaを入力して読み込まれたか確認します。
詳細
Inertia.js + Hono + hono/jsx で SPA のようなアプリを構築する
Inertia.js を使うと、フロントエンドを別途用意したり JSON API レイヤーを書いたりせずに、シングルページアプリを構築できます。サーバーは Inertia レスポンス(ページ名 + props)を返し、薄いクライアントアダプターが現在のページコンポーネントを動的に切り替えます。サーバーに Hono、クライアントに hono/jsx を使えば、React を使わず、単一の TypeScript スタック内で SPA のようなインタラクティブ性を実現できます。
サーバーサイドレンダリングの hono/jsx だけでは足りなくなったとき、つまり、クライアントサイドの状態管理、楽観的 UI、またはリッチなフォームが必要になったときに活用してください。
スタック
- サーバー:
hono+@hono/inertia。inertia({ rootView })ミドルウェアによって、Inertia レスポンスを返すc.render(name, props)が追加されます。 - クライアント:
@ts-76/inertia-hono-jsx。@inertiajs/coreとhono/jsx/domを基盤として、createInertiaApp、<Link>、<Form>、<Head>、useForm、useHttpなどを提供します。 - バンドル / SSR: Vite(Workers にデプロイする場合は
@cloudflare/vite-pluginを使用)と、アセットを連携するためのvite-ssr-componentsを使います。Hono のcloudflare-workers+viteスターターテンプレートから始めてください。Vite と Workers の連携はすでに設定されています:https://github.com/honojs/starter/tree/main/templates/cloudflare-workers+vite。 - ページの型付け:
@hono/inertia/viteがpages.gen.tsを生成するため、PagePropsFor<Name>はc.render(Name, ...)に渡される props として解決されます。
参考例
yusukebe/hono-inertia-example は標準的な構成例です(Cloudflare Workers 上の Hono + Inertia)。React で書かれているため、app/client.tsx と app/root-view.tsx をそのままコピーしないでください。これらは @ts-76/inertia-hono-jsx と hono/jsx を使う形に書き直す必要があります。サーバー側(app/server.ts、ルートの構成、Vite の設定、wrangler.jsonc、@hono/zod-validator によるバリデーション)はそのまま使えます。
https://github.com/yusukebe/hono-inertia-example
サーバーの概要
// app/server.ts
import { Hono } from 'hono'
import { inertia } from '@hono/inertia'
import { rootView } from './root-view'
const app = new Hono()
app.use(inertia({ rootView }))
const routes = app
.get('/', (c) => c.render('Home', { message: 'Hono x Inertia' }))
.get('/users', (c) => c.render('Users/Index', { users: listUsers() }))
.get('/users/:id{[0-9]+}', (c) => {
const id = Number(c.req.param('id'))
const user = findUser(id)
if (!user) return c.notFound()
return c.render('Users/Show', { user })
})
export default routes
c.render(name, props) は、最初のリクエストでは HTML ドキュメント全体を、以降のクライアントナビゲーションでは JSON の Inertia ページオブジェクトを返します。@hono/inertia は X-Inertia リクエストヘッダーを確認して判断します。
クライアントの概要(hono/jsx)
// app/client.tsx
import { createInertiaApp } from '@ts-76/inertia-hono-jsx'
createInertiaApp({
resolve: (name) => {
const pages = import.meta.glob('./pages/**/*.tsx', { eager: true })
return pages[`./pages/${name}.tsx`]
},
})
カスタムの setup を指定しない場合、アダプターが <App /> をマウントします。ルートに data-server-rendered がある場合は(hono/jsx/dom の)hydrateRoot を使い、それ以外の場合は createRoot を使います。
ページコンポーネント(hono/jsx)
// app/pages/Users/Index.tsx
import { Head, Link, type PageComponent } from '@ts-76/inertia-hono-jsx'
const UsersIndex: PageComponent<'Users/Index'> = ({ users }) => (
<main>
<Head title='Users' />
<h1>Users</h1>
{users.map((u) => (
<Link href={`/users/${u.id}`} key={u.id}>
{u.name}
</Link>
))}
</main>
)
export default UsersIndex
PageComponent<'Users/Index'> は、サーバールートから @hono/inertia/vite によって生成される pages.gen.ts から prop の型を取得します。そのため、サーバーとクライアントの props に不一致があると、実行時エラーではなくビルドエラーになります。
デフォルト設定
tsconfig.jsonでjsxImportSource: "hono/jsx"を設定します。- Vite のクライアントとアセットタグを挿入するには、
root-view.tsxでvite-ssr-components/hono(/reactではありません)を使います。 @cloudflare/vite-pluginを使ってデプロイし、wrangler deployで SSR ワーカーとクライアントバンドルをまとめて配布します。
注意点
@ts-76/inertia-hono-jsxはコミュニティ提供のライブラリであり、README によるとhono/jsxのサポートは「部分的」です。ブラウザーでのレンダリングにはhono/jsx/domを対象としていますが、サーバーサイドレンダリングの忠実度は React と同等ではありません。複雑なページでは SSR の出力を必ず確認してください。- Inertia は API フレームワークではありません。Inertia を使わないクライアント(モバイル、サードパーティ)にも同じデータを提供する必要がある場合は、別途 JSON エンドポイントを構築してください。Inertia レスポンスの形式を使い回そうとしないでください。
- アセットのバージョン管理:
X-Inertia-Versionがクライアントバンドルのハッシュと一致するようにしてください。一致しない場合、クライアントはナビゲーションのたびに通知なく強制的に全体を再読み込みします。 - Cloudflare Workers では、クライアントバンドルを Workers Assets 経由で配信してください(
@cloudflare/vite-pluginが処理します)。fetch で R2 から取得する方法は使わないでください。
さらに詳しく
- Inertia プロトコルそのものについて:短く、全文を読む価値があります:https://inertiajs.com/。
- hono/jsx アダプターの API と型付けの仕組み:https://github.com/ts-76/inertia-hono-jsx。
- コピーする参考構成:https://github.com/yusukebe/hono-inertia-example。