Skip to content

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.jsmarkdown-it のハイライトオプションにプラグイン形式で統合。これにより、プレビュー画面でも本番環境と同じ色付けが即座に反映されます。
  • CSSの同期: VitePressの標準テーマである .vp-doc クラスをプレビュー枠に適用することで、プレビューと公開画面の見た目の差異をゼロに近づけました。

データの安全性とリロード対策

VitePressの「ファイル変更を検知してブラウザを強制リロードする」というHMR(Hot Module Replacement)機能は、エディタ開発においては「編集中データの消失」というリスクになります。

  • localStorageキャッシュ戦略: watch 機能を使い、タイトル・タグ・内容の変更を数ミリ秒の遅延で常に localStorage へ同期。万が一のリロードやブラウザのクラッシュ時にも、onMounted フックで即座に執筆内容が復元される「ステートレスな永続化」を実現しました。
  • ブラウザ離脱プロンプト: beforeunload イベントをハンドルし、isDirty フラグに基づいて警告を出すことで、不慮のブラウザバックによるデータ損失を防止しました。