VitePressにひとこと投稿機能を実装する

VitePressにひとこと投稿機能を実装する

ひとこと投稿機能があれば、静的ブログの記事公開に伴う手間や心理的な負担を減らし、短い思いつきをいつでも共有できます。Cloudflare Workersでバックエンドを実装し、KVで投稿を保存・管理します。フロントエンドはVitePressにVueコンポーネントを埋め込み、投稿を手軽に表示してブログに動きと交流の場を加えます。

はじめに

動的ブログには、ひとこと投稿の機能がよくあります。本質的には特殊な記事ですが、動的ブログの即時性を生かして、書いたらすぐ公開できます。

静的ブログはローカルやサーバーでHTMLへコンパイルしてからデプロイするため、即時性には欠けます。長い記事ならパソコンの前で書いてGitで公開しても、それほど面倒ではありません。ただ、ひとこと投稿のためにパソコンを開くのは心理的な負担が大きいです。スマートフォンでGitを操作するのも面倒で、あまりスマートではありません。考えるうちに、もう投稿しなくていいかとなってしまいます。

そこで、ひとこと投稿システムのフロントエンドとバックエンドを作りました。このブログのひとことがその完成形です。バックエンドはCloudflare Workersで、保存先も近場にある太っ腹なCloudflareのKVにしました。簡単な管理画面も用意しています。ブログはVitePressなので、フロントエンドはVueコンポーネントとして作り、専用ページに埋め込みました。

フロント側は実際のページを見てもらうとして、管理画面はこんな感じです。 ひとこと投稿の管理画面

バックエンド:Cloudflare Workers + KV

概要

バックエンドには次の機能があります。

  • 投稿の追加・編集・削除(基本機能)。
  • 管理画面とすべての書き込みAPIに認証を設け、十分な安全性を確保。
  • markedによるMarkdownのリアルタイムプレビュー。

KVにはindexというキーを保存し、その値を全投稿のインデックスとなるUID配列にします。各投稿はuidをキーとして個別に保存し、値は次の形式です。

js
{
    "uid":"唯一 id",
    "createTime":"发布时间",
    "content":"说说内容",
}

実装

まず、投稿用のキーと値を保存するCloudflare KV名前空間を作ります。アカウントホーム → ストレージとデータベース → KVで作成をクリックします。名前は覚えておけば何でもよく、私は単純にmemosにしました。

次に、処理を担当するCloudflare Workerを作成します。アカウントホーム → コンピューティング(Workers)→ WorkersとPagesから作成します。これも名前は自由で、私はmemos-apiにしました。作成したWorkerの名前をクリックして詳細を開き、設定 → バインディングで追加します。KV名前空間を選び、変数名をKV、名前空間を先ほどのもの(私の場合はmemos)にします。これでコードからenv.KVで直接memosを操作できます。最後に上部右側のコードを編集をクリックします。

ここからはコードの時間です!

まず管理画面のHTML、CSS、JavaScriptを置くindex.htmlを作成します。

JavaScriptを見ると、バックエンドのエンドポイントは次のとおりです。

  • POST /api/auth:ページの認証。
  • GET /api/memos:投稿を取得。ページネーション対応。
  • POST /api/memos:新規投稿。
  • PUT /api/memos/{uid}:投稿を更新。
  • DELETE /api/memos/{uid}:投稿を削除。

続いてworker.jsでこれらを実装します。

先頭の定数3つを設定してください。

  • CORRECT_PASSWORD:管理画面のパスワード。
  • CALLBACK_URL:投稿の公開・更新・削除後に呼び出すコールバックURL。
  • ALLOWED_ORIGINS:CORSでアクセスを許可するドメインのリスト。少なくともブログと管理画面の2つのドメインを入れます。

設定したらデプロイをクリックします。

GFWの影響で標準のworkers.devドメインはアクセスしにくいため、Workerには独自ドメインを設定したほうがよいです。memosの詳細 → 設定 → ドメインとルートでカスタムドメインを追加し、Cloudflareで管理しているドメインを入力します。worker.jsALLOWED_ORIGINSにも追加してください。

これで管理画面が使えます。URLはhttps://{自分のドメイン}/manageです。パスワードを入力したら、あとは楽しんでください!

フロントエンド

VitePressのおかげで、Vueコンポーネントとしてひとこと投稿の画面を書き、ブログへ簡単に埋め込めます。

まずmarkedjsをインストールします。pnpmなら次のコマンドです。

shell
pnpm add marked

テーマ設定ファイル(通常はdocs/.vitepress/theme/index.tsですが、パスや拡張子は異なる場合があります)と同じ階層にcomponentsフォルダーを作り、その中にmemos.vueを作成します。フォルダーがすでにあればそのまま使います。

コード内の{你的域名}をCloudflare Workerのドメインに置き換えてください。

気づいた方もいると思いますが、このコンポーネントの初期データはWorkerのAPIからではなく、JSONファイル(import memosRaw from '../../../../memos.json')から読み込みます。「もっと読む」を押したときだけWorkerへ取りに行きます。理由は次のとおりです。

  • 使い勝手のためです。初期データをAPIから取得すると、ページを開いてから取得完了まで空白になり、体験がよくありません。
  • 節約のためです。Cloudflare Workersの無料枠にはリクエスト数の制限があり、初期データを静的に読むと大きく削減できます。

memos.jsonには、ビルド時にAPIから取得した先頭10件の投稿を保存します。WorkerにCALLBACK_URLがあるのは、新規投稿や先頭10件の編集・削除時に再ビルドするためです。具体的なURLはデプロイ先のプラットフォームに合わせて調べてください。全件を動的に取得するなら、このコールバックは不要です。

次のコードでビルド時にmemos.jsonを生成します。テーマ設定ファイル(通常はdocs/.vitepress/theme/index.ts。パスや拡張子は異なる場合があります)と同じ階層にutilsフォルダーを作り、その中にmemos.jsを作成します。既存のフォルダーがあればそれを使います。

ブログルートのpackage.jsonを編集し、devとbuildの両方のコマンドの前にnode docs/.vitepress/theme/utils/memos.jsを追加します。追加箇所は構成によって異なるので、私の例を載せます。

json
{
  ...
  "scripts": {
    "dev": "node docs/.vitepress/theme/utils/memos.js && vitepress dev docs",
    "build": "node docs/.vitepress/theme/utils/memos.js && vitepress build docs",
    "serve": "vitepress serve docs"
  },
  ...
}

これでdevでもbuildでも最初にmemos.jsが実行され、ブログルートにmemos.jsonが生成されます。ディレクトリ構成に合わせてmemos.vueのimportパスを調整してください。

コンポーネントとデータがそろったので、次はグローバルコンポーネントとして登録します。

テーマ設定ファイル(通常はdocs/.vitepress/theme/index.ts。パスや拡張子が異なる場合があります)で読み込み、登録します。

js
...
import Memos from './components/memos.vue'
...
export default {
    ...
    enhanceApp({ app }) {
        ...
        app.component('Memos', Memos);// [!code highlight]
    }
} satisfies Theme

これでブログのどこでも<Memos />で直接コンポーネントを埋め込めます。

最後に、このコンポーネント専用の独立ページを作ります。

えっ、VitePressで独立ページを使ったことがないですって?

まずルートにpagesフォルダーを作ります。次にVitePress本体の設定ファイル(テーマ設定ではありません。通常はdocs/.vitepress/config.tsですが、パスや拡張子が違うこともあります)にrewritesルール'pages/:file.md': ':file.md'を追加します。これでpages配下のファイルに/ファイル名で直接アクセスできます。rewritesについては公式ドキュメントを参照してください。

pagesフォルダーにbalabala.mdを作り、次の内容を書きます。

markdown
---
title: 碎碎念
hidden: true
comment: false
sidebar: false
aside: false
readingTime: false
showMeta: false
---

<Memos />

これで完成です。

VPSの特定の通信をWARP経由のIPv6で外に出す
OPNsenseで透過プロキシとトラフィック振り分けを実現する