zpaper-draft

Type to search...

to open search from anywhere

ccdocの技術選定ミスを修正した話: Docusaurus → Astro

概要

Claude Codeのリソースビューアアプリ「ccdoc」をDocusaurusからAstroに移行した。ただ、これはDocusaurus vs Astroの優劣比較みたいな話ではない。バンドラーベースのアーキテクチャと、ファイル単位ビルドのアーキテクチャの違いが、ccdocというユースケースに対してどう効くかという話。Docusaurusはドキュメントウェブサイトのためのフレームワークとして優秀で、その用途に使うなら良い選択肢であることに変わりはない。そのまとめ。

ccdocとは

~/.claude/以下には自分のClaude Code環境をカスタマイズするためのファイルが大量にある。commands、agents、skills、各リポジトリのCLAUDE.mdなど。これらはMarkdownファイルで、日常的に追加・編集・削除される。

ccdocはこれらのリソースをドキュメントサイトとして閲覧できるElectronアプリ。サイドバーでファイルを選んで、本文を読んで、TOCで見出しにジャンプできる。検索もできる。doc-history機能もあり、各ドキュメントのgitコミット履歴を表示して差分を比較できる。

バンドラーアーキテクチャとファイルの動的変更

DocusaurusはReactベースのドキュメントサイトフレームワークで、内部的にはWebpackというバンドラーを使っている。バンドラーアーキテクチャの特徴は、ビルド時にすべてのファイルを依存関係グラフとして結合するところにある。

既存ファイルの編集は、電球の交換みたいなもので、配線はそのままだからHMR(Hot Module Replacement)でさっと反映される。ただ、ファイルの追加・削除はそうはいかない。Docusaurusでは各ドキュメントが1つのルートになるので、ファイルが追加されると、ルートの再生成、サイドバーの再計算、仮想モジュールの再構築、そしてwebpackのフル再コンパイルが走る。部屋を増改築するようなもので、配線図全体を引き直す必要がある。

これはwebpackやDocusaurusの欠陥ではない。バンドラーアーキテクチャは、エントリーポイントがある程度安定していることを前提に設計されている。一度建てた構造の上で中身を入れ替える(ファイル編集)のは得意だが、構造自体を頻繁に変える(ファイル追加・削除)のは、アーキテクチャとして向いていない。これはwebpackに限った話ではなく、バンドラーベースのフレームワーク全般に当てはまる構造的な特性。

ドキュメントウェブサイトならこれは問題にならない。コンテンツを書いてビルドしてデプロイするという流れでは、ファイルの追加・削除はたまにしか起きないし、そのときにフルビルドが走るのは普通のこと。ただ、ccdocは「ウェブサイト」ではなく、~/.claude/配下のファイルがユーザーの操作で常に追加・削除されるビューアアプリ。常に増改築し続ける状況で、一度建てることを前提にした構造を使っていたというのがミスマッチだった。

devサーバーを使わずにビルドだけする方式も検討したが、Docusaurusのビルドプロセスもwebpackベースなので、同じ依存関係グラフの構築が毎回走る。プログラマティックなビルドAPIも使いにくく、Node.jsのchild processからdocusaurus buildを呼ぶことはできるが、ビルドプロセス全体が重く、細かい制御がしづらい。

Astroのアーキテクチャが合っていた理由

Astroは各ページを独立してビルドする。ページ間の依存関係グラフがないので、ファイルが追加されたら「そのページをビルドしてdist/に置く」で終わる。配線図を引き直す必要がなく、ファイルの追加・削除がアーキテクチャ上のコストにならない。

もう少し具体的にいうと、Astroの構造は「監視 → ページビルド → 配信」というシンプルな流れになる。ナビゲーションやサイドバーの更新はビルドの中で個別に処理される。全ページをグラフとして結合するステップがないので、ファイル数が増えてもビルドの複雑さが爆発しない。

ccdocのように「ファイルが日常的に追加・削除される環境で、変更を素早く反映したい」というユースケースに対しては、このファイル単位の独立ビルドというアーキテクチャが合っていた。

解決策は複数あった

この問題に対して、取れるアプローチは複数あった。

  • SPA + Node.jsバックエンド方式 — Reactなどでフロントエンドを作り、Electronのmainプロセス側でNode.jsのファイル読み込み・変換・配信を行う。フロントエンドは純粋なSPAで、バックエンドAPIからMarkdownのHTMLを受け取って表示する
  • Astroのbuild API方式 — astro buildで静的HTML生成 → Node.jsのhttpモジュールで配信 → fs.watchでファイル変更監視 → リビルド
  • その他のSSGツール — ビルドCLIがクリーンで、外部プロセスから呼びやすいものなら何でも良い

SPA方式は最も柔軟だが、ドキュメントサイトのUI(サイドバー、TOC、検索、コードハイライト等)を全部自前で作る必要がある。Astroの方式なら、Astroが持っているドキュメントサイト向けの仕組みを利用しつつ、ビルドプロセスをNode.jsから制御できる。

Astroを選んだのは、astro buildというCLIがクリーンで、Node.jsのchild processから呼びやすかったから。それだけ。

ElectronにはNode.jsランタイムが組み込まれているので、どのアプローチを取るにしてもバックエンド側の実装(ファイル監視、ビルド、静的配信)はNode.jsで自由に書ける。フロントエンドのフレームワーク選択とバックエンドのアーキテクチャは独立した話になる。

Astroでの実装

Astroへの移行後のアーキテクチャは以下。

  1. アプリ起動時にastro builddist/に全ページの静的HTMLを生成
  2. Node.jsのhttpモジュールでdist/をlocalhost
  3. fs.watch~/.claude/配下の.mdファイル変更を監視
  4. 変更検知 → 2秒デバウンス → SKIP_DOC_HISTORY=1付きでリビルド → 全Electronウィンドウを自動リロード

ファイルを追加・編集・削除したら、数秒後に自動で反映される。

doc-historyキャッシュによる高速リビルド

フルビルドには約2.5分かかる。そのうち約2分がdoc-history(git履歴)の生成。ccdocは各ドキュメントのgitコミット履歴をJSONに変換して、2つのリビジョン間の差分を比較できる機能を持っている。

ccdoc の Diff ビュー。コミット履歴と差分表示

この処理が重いので、以下のキャッシュ戦略を取っている。

  • SKIP_DOC_HISTORY=1環境変数でgit履歴生成をスキップ
  • 初回ビルド時にdist/doc-history/.doc-history-cache/にコピー
  • ウォッチ経由のリビルドではSKIP_DOC_HISTORY=1でビルドした後、キャッシュからdist/doc-history/を復元

結果、初回ビルドは約2.5分だが、ウォッチ経由のリビルドは約5秒で完了する。

SPA的な体験は失われたのか

DocusaurusはクライアントサイドルーティングによるSPAなので、ページ遷移してもサイドバーのスクロール位置が維持される。ccdocのスキル一覧は80以上あり、サイドバーが長い。

ccdoc のスキルページ表示。サイドバーが長い

Astroに移行すると、素の状態ではページ遷移のたびにサイドバーが先頭に戻る。元の記事ではこれを「トレードオフ」として書いていたが、実際にはAstroのView Transitions APIで解決できた。

具体的にやったことは以下。

  • ClientRouterコンポーネントでView Transitionsを有効化。これでページ遷移がフルリロードではなくスムーズなトランジションになる
  • サイドバーの<aside>要素にtransition:persistディレクティブを付与。これでページ遷移してもサイドバーのDOMが維持される
  • スクロール位置の保存・復元 — astro:before-swapイベントでscrollTopを保存し、astro:after-swapイベントで復元する
  • Reactサイドバーアイランドがastro:after-swapイベントを検知してURLの変化に応じてアクティブなアイテムのハイライトを更新する

transition:persistでサイドバーのDOM自体が維持されるので、スクロール位置も開閉状態も保たれる。Reactアイランドはdocument.addEventListener('astro:after-swap', ...)でページ遷移を検知して、自分の状態を更新する。

結果として、SPA的なページ遷移体験はAstroでも実現できた。View Transitionsの仕組みを理解して正しく使えば、MPA(Multi Page Application)でもSPAに近い体験を作れる。ただし、View Transitionsが何をしているのかを理解した上で設定する必要はある。

まとめ

これはDocusaurusとAstroの優劣比較ではなく、アーキテクチャとユースケースのマッチングの話。Docusaurusはドキュメントウェブサイトのためのフレームワークとして優秀で、その用途に使うなら良い選択肢であることに変わりはない。

バンドラーアーキテクチャは、安定したエントリーポイントの上でコンテンツを編集していくユースケースに強い。ファイル構造が頻繁に変わる環境では、依存関係グラフの再構築コストが問題になる。ccdocは後者のユースケースだったので、ファイル単位で独立にビルドできるAstroのアーキテクチャが合っていた。

Astroに移行したのは、astro buildのCLIがNode.jsから呼びやすかったから。View TransitionsでSPA的な体験も確保できた。アーキテクチャの選択をユースケースに合わせ直した結果、うまく収まった。