VitePressにおけるカスタムAPI実装とエディタ画面開発
VitePress環境を「静的サイトビュワー」から「ローカル完結型CMS」へと拡張した際の、アーキテクチャおよび実装に関する詳細な知見の記録。
1. システムアーキテクチャ:SSGと動的機能の融合
VitePressは本来、ビルド時にMarkdownを静的なHTMLに変換するSSG(静的サイトジェネレーター)ですが、その基盤であるViteのデバックサーバー機能を拡張することで、開発時に強力な動的バックエンドを持たせることが可能です。
開発時とプロダクション時の役割分離
- 開発フェーズ(Dynamic Mode): Viteの
configureServerフックを利用し、ブラウザからのリクエスト(保存・Git操作)をNode.js環境で処理します。 - 運用フェーズ(Static Mode): 書き出されたMarkdownファイルが通常のVitePressビルドプロセスに乗り、高速な静的サイトとしてデプロイされます。
この構成により、DB不要で、Gitを唯一の正(Source of Truth)とする「Local-First CMS」の構築が可能になりました。
2. ViteプラグインによるバックエンドAPIのエコシステム
localRepoWritePlugin を通じて、Node.jsの標準モジュールやGit CLIと連携するAPIを実装しました。
Connectミドルウェアの活用
Viteのデブサーバーは内部で Connect を使用しています。APIリクエスト(/api/*)をインターセプトする際、以下の点に留意しました。
- 非同期ボディパース: Node.jsの
IncomingMessageはストリームであるため、req.on('data')を用いてチャンクごとにデータを受け取り、完了後にJSON.parseを行う伝統的かつ確実な処理を採用しました。 - Git操作の原子性(Atomicity):
git add前にgit resetを明示的に実行するロジックを導入しました。これにより、VS Code等で他のファイルを並行して編集していても、エディタから保存した特定のファイルのみを確実にコミット対象とする「アトミックなコミット」を保証しています。- 外部プロセス(
execAsync)の実行管理により、ブラウザからの操作でローカルリポジトリが壊れない堅牢性を確保しました。
3. Vue 3 Composition APIによる高度なエディタ実装
MemoEditor.vue は、単なるテキストエリアではなく、開発体験(DX)を最大化するためのロジックを搭載しています。
リアルタイム・レンダリング・パイプライン
VitePressが提供する markdown-it と同じエンジンをブラウザ側で構成しました。
- シンタックスハイライト:
highlight.jsをmarkdown-itのハイライトオプションにプラグイン形式で統合。これにより、プレビュー画面でも本番環境と同じ色付けが即座に反映されます。 - CSSの同期: VitePressの標準テーマである
.vp-docクラスをプレビュー枠に適用することで、プレビューと公開画面の見た目の差異をゼロに近づけました。
データの安全性とリロード対策
VitePressの「ファイル変更を検知してブラウザを強制リロードする」というHMR(Hot Module Replacement)機能は、エディタ開発においては「編集中データの消失」というリスクになります。
- localStorageキャッシュ戦略:
watch機能を使い、タイトル・タグ・内容の変更を数ミリ秒の遅延で常にlocalStorageへ同期。万が一のリロードやブラウザのクラッシュ時にも、onMountedフックで即座に執筆内容が復元される「ステートレスな永続化」を実現しました。 - ブラウザ離脱プロンプト:
beforeunloadイベントをハンドルし、isDirtyフラグに基づいて警告を出すことで、不慮のブラウザバックによるデータ損失を防止しました。