mcpField
H
hono-inertia

hono-inertia

github.com/yusukebe開発3699 回インストール23 週間前に更新
リポジトリ

hono-inertia について

Hono、Inertia.js、hono/jsx を使い、フロントエンドを分けずに SPA のようなアプリを構築する TypeScript 開発者向けガイドです。ページプロパティの型生成や、Vite と Cloudflare Workers のデプロイ設定も扱います。

主な機能

  1. 01Hono から Inertia ページを返す
  2. 02hono/jsx によるクライアントページ作成
  3. 03ページプロパティ型の生成
  4. 04Vite によるバンドルと SSR
  5. 05Cloudflare Workers のデプロイ設定

活用シーン

  1. 01Hono アプリへのクライアント状態の追加
  2. 02楽観的 UI と複雑なフォームの構築
  3. 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 から取得する方法は使わないでください。

さらに詳しく

コメント