🐷

HonoX上でshadcnを動かしてみた

に公開

はじめに

こんにちは!satto workspaceでプロダクトエンジニアをしている ryohei oyama(@ryohei_oyama)です。

HonoXでshadcn/uiを使いたい!でも設定どうするの...?

実はTailwind設定とaliasを設定するだけで動きます。この記事ではその手順を解説します。

セットアップ手順

1. プロジェクト作成

npm create hono@latest my-app

対話形式で進みます:

  • テンプレート選択で x-basic を選択
  • 他はデフォルトでOK
cd my-app
npm install

2. React 19を追加

HonoXのx-basicテンプレートにはReactが含まれていないため、追加します。

npm install react react-dom
npm install -D @types/react @types/react-dom

3. Tailwind CSS v4を追加

npm install tailwindcss @tailwindcss/vite

4. Vite設定でTailwindとaliasを設定

vite.config.tsを以下の内容で作成:

vite.config.ts
import build from '@hono/vite-build/node'
import honox from 'honox/vite'
import adapter from '@hono/vite-dev-server/node'
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
import path from "path"

export default defineConfig(({ mode }) => {
  if (mode === 'client') {
    return {
      build: {
        rollupOptions: {
          input: ['./app/client.ts', './app/style.css'],
          output: {
            entryFileNames: 'static/client.js',
            chunkFileNames: 'static/assets/[name]-[hash].js',
            assetFileNames: 'static/assets/[name].[ext]'
          }
        },
        emptyOutDir: false
      },
      plugins: [tailwindcss()],
      resolve: {
        alias: {
          "@": path.resolve(__dirname, "./app"),
        },
      },
    }
  } else {
    return {
      ssr: {
        external: ['react', 'react-dom']
      },
      plugins: [
        honox({
          devServer: { adapter },
          client: { input: ['./app/style.css'] }
        }),
        tailwindcss(),
        build()
      ],
      resolve: {
        alias: {
          "@": path.resolve(__dirname, "./app"),
        },
      },
    }
  }
})

ポイント:

  • mode === 'client'でクライアントビルドとサーバービルドを分岐
  • tailwindcss()プラグインを両方のモードで追加
  • @エイリアスを./appに設定
  • ssr.externalでReactを外部化(SSRで重複読み込みを防ぐ)

5. TypeScript設定

tsconfig.jsonにReact設定とパスエイリアスを追加:

tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    "baseUrl": ".",
    "paths": {
      "@/*": ["./app/*"]
    }
  }
}

ポイント:

  • jsx: "react-jsx" - React 17以降の新しいJSX変換を使用
  • jsxImportSource: "react" - JSXのインポート元を明示

6. Reactクライアント設定

HonoXでReact 19を使うためにapp/client.tsを作成:

app/client.ts
import { createClient } from 'honox/client'

createClient({
  hydrate: async (elem, root) => {
    const { hydrateRoot } = await import('react-dom/client')
    hydrateRoot(root, elem)
  },
  createElement: async (type: any, props: any) => {
    const { createElement } = await import('react')
    return createElement(type, props)
  }
})

7. レンダラー設定

app/routes/_renderer.tsxを作成してCSSを読み込む:

app/routes/_renderer.tsx
import { reactRenderer } from '@hono/react-renderer'

export default reactRenderer(({ children }) => {
  return (
    <html lang="ja">
      <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>
            <link href="/static/assets/style.css" rel="stylesheet" />
          </>
        ) : (
          <>
            <script type="module" src="/app/client.ts"></script>
            <link href="/app/style.css" rel="stylesheet" />
          </>
        )}
      </head>
      <body>{children}</body>
    </html>
  )
})

ポイント:

  • import.meta.env.PRODで開発環境と本番環境を分岐
  • 開発時は/app/style.css、本番時は/static/assets/style.cssを読み込む

8. CSS変数の定義

app/style.cssを作成:

app/style.css
@import "tailwindcss";

shadcn/uiを使う

1. 初期化

npx shadcn@latest init

設定のポイント:

  • Style: お好みで(New YorkまたはDefault)
  • Base color: お好みで
  • CSS variables: yes
  • CSSパスは app/style.css に設定

2. コンポーネント追加

npx shadcn@latest add button

これだけ!app/components/ui/button.tsxが生成されます。

完成したディレクトリ構成

.
├── app/
│   ├── components/
│   │   └── ui/
│   │       └── button.tsx       # shadcn/uiのButtonコンポーネント
│   ├── lib/
│   │   └── utils.ts             # cn()ユーティリティ
│   ├── routes/
│   │   ├── _renderer.tsx        # レイアウト
│   │   └── index.tsx            # トップページ
│   ├── client.ts
│   ├── global.d.ts
│   ├── server.ts
│   └── style.css                # Tailwind CSS + CSS変数
├── components.json              # shadcn/ui設定
├── package.json
├── tsconfig.json                # TypeScript設定
└── vite.config.ts               # Vite設定(alias + Tailwind)

使ってみる

app/routes/index.tsxで使用:

app/routes/index.tsx
import { createRoute } from 'honox/factory'
import { Button } from '@/components/ui/button'

export default createRoute((c) => {
  return c.render(
    <div className='flex gap-2 p-4'>
      <Button>デフォルト</Button>
      <Button variant="secondary">セカンダリ</Button>
      <Button variant="outline">アウトライン</Button>
    </div>
  )
})
npm run dev

これで完了です!

HonoXとshadcn/uiのデモ画面
shadcn/uiのButtonコンポーネントが動作している様子

よくあるエラー

パスエイリアスが解決されない

Cannot find module '@/components/ui/button'

vite.config.tstsconfig.json両方にaliasを追加してください。

スタイルが効かない

app/style.cssにTailwindの@import "tailwindcss"があるか確認してください。

まとめ

HonoXでshadcn/uiを使うには:

  1. Tailwind CSS v4のViteプラグインを追加
  2. パスエイリアス@/*を設定(Vite + TypeScript)
  3. CSS変数を定義

これだけでshadcn/ui CLIが使えます。サクッと設定して、美しいUIコンポーネントを使いましょう!

余談

私の理解不足かもしれませんが、Aceternity UIのコンポーネントも試してみたところ、アニメーション系が全然動きませんでした...

shadcn/uiのようなシンプルなスタイリングベースのコンポーネントは問題なく動作しますが、framer-motionなどを使った複雑なアニメーションは難しそうです。そもそもHonoXは作者のyusukebeさんの記事にもある通り「インタラクションの少ないWebサイト」向けのMPAフレームワークなので、納得の結果でした。

参考リンク

ソフトバンク株式会社_satto開発チーム

Discussion