りゅーそうブログ

ブログのカテゴリーをプレビュー画面で選択できるようにした

テクノロジー 開発

2026/03/31 16:33

ブログのカテゴリーをプレビュー画面で選択できるようにしました。

このブログではカテゴリーごとに文字色や背景色をAPIデータで持っています。

それを毎回選択するのが面倒で設定するのを後回しになってしまっていました。データは以下のようになっています。

{
    "id": "ga8t3t2i6h",
    "createdAt": "2024-09-21T08:16:30.546Z",
    "updatedAt": "2026-03-30T07:41:35.024Z",
    "publishedAt": "2024-09-21T08:16:30.546Z",
    "revisedAt": "2026-03-30T07:41:35.024Z",
    "name": "くだらない話",
    "bg": "#F8EEF4",
    "border": "#7A5367"
}

ただ、記事を書くたびにカテゴリーや色を考えるのが地味に面倒で、設定を後回しにしがちでした。
そこで今回は、プレビュー画面上で記事内容からカテゴリーをAIに推測させ、そのまま自動作成・選択できるようにしました。

別案として、MCPなどを活用してAIチャットなどから入力する方法もあるのかもとも考えました。

画面プレビューは管理画面から直接リンクされているので、その点運用もしやすそうです。なので今回はプレビュー画面にモリモリAI機能を実装する方針で進めていきました。

技術スタック

このブログは Astroで作成したクライアントをVercelにデプロイをしています。まずAstroで今回のような動的なページを作るには、prerender = false を設定します。

export const prerender = false;

これを各ファイルで設定することによって、オンデマンドレンダリングが可能になります。事前レンダリングがOFFになるので、VercelのAPI Routeなどでエンドポイントを作成して、要素を動的に更新するページを作成することが可能になります。

https://docs.astro.build/ja/guides/on-demand-rendering/

今回はVercelにデプロイをしているので、@astrojs/vercel を利用します。

またAIの利用にはOpenAIのAPIを利用します。

API Routesの作成

処理の大まかな流れは以下です。

  • Preview画面からAPI Routeを叩く
  • OpenAIに記事本文と既存カテゴリー一覧を渡す
  • 既存カテゴリーを選ぶ、または新規カテゴリー案を返してもらう
  • 必要に応じてmicroCMSのカテゴリーAPIにPOST
  • 最後に記事のcategoriesをPATCHして下書き更新する

今回はOpenAIのAPIキーとmicroCMSのAPIキーを利用するためAPI Routesでキーを扱います。記事では省略しますが、これらの保護のため何らかの認証を入れるのがベターだと思います。

大まかに処理の概要を紹介します(詳細な記事は需要があれば....)。OpenAIのクライアントを作成して、

const getOpenAIClient = () => {
  const apiKey = import.meta.env.OPENAI_API_KEY;
  if (!apiKey) {
    throw new PreviewRouteError(500, "OPENAI_API_KEY が設定されていません。");
  }

  openAIClient ??= new OpenAI({ apiKey });

  return openAIClient;
};

以下のようなプロンプトを投げます(ひとまず雑な解説ですいません)。

const response = await getOpenAIClient().responses.create({
      model: OPENAI_MODEL,
      instructions: [
        "あなたは日本語ブログのカテゴリー編集者です。",
        "カテゴリーは記事固有の話題を抜き出すタグではなく、サイト全体で再利用する大きな分類です。",
        "既存カテゴリーを優先して選び、適切なものがない場合だけ新規カテゴリーを提案してください。",
        `合計 ${MAX_BLOG_CATEGORIES} 件以内にしてください。`,
        "曖昧な場合は無理に増やさず、少なめに返してください。",
        "既存カテゴリーの言い換えや重複になる新規カテゴリーは提案しないでください。",
        "新規カテゴリー名は短く、自然な日本語で、他の記事にも使い回せる上位概念にしてください。",
        "記事タイトルや本文に出てきた具体的な名詞や出来事を、そのままカテゴリー名にしてはいけません。",
        "スポット名、店名、作品名、イベント名、企画名、固有名詞、季節の一場面など、記事固有の表現は避けてください。",
        "細かすぎる分類より、読者が一覧で理解しやすい広めの分類を優先してください。",
        "新規カテゴリーには、そのテーマに合う背景色(bg)と文字・枠線色(border)を #RRGGBB 形式で必ず付けてください。",
        "背景色は淡めで、文字・枠線色は背景色の上で十分に読みやすい濃い色にしてください。",
      ].join(" "),
      input: buildAiCategorizationInput(blog, categories),
      text: {
        format: {
          type: "json_schema",
          name: "preview_blog_category_suggestion",
          strict: true,
          schema: AI_CATEGORY_SUGGESTION_SCHEMA,
        },
      },
    });

このような処理をPreview画面から叩きます。プラスボタンを叩いたらこのエンドポイントを叩くイメージです。

このエンドポイントを叩くと、以下のようにカテゴリーの候補をAIがアウトプットします。新規カテゴリーが必要であれば作成・既存のカテゴリーがあればその中から自動的に選択してもらう運用にしてみました。

「カテゴリーを設定」をクリックすると、実際にmicroCMSの管理画面のコンテンツにも反映されます。

microCMSのPATCH APIのbodyに投げて記事を更新・新規にカテゴリーを作成する場合はカテゴリーAPIにPOSTリクエストを行うというイメージです。

export const updateBlogCategoriesDraft = async (
  contentId: string,
  categoryIds: string[],
) => {
  return await writeClient.update<BlogCategoryUpdateInput>({
    endpoint: "blogs",
    contentId,
    content: {
      categories: categoryIds,
    },
    isDraft: true,
  });
};

export const createCategory = async (input: CategoryWriteInput) => {
  return await writeClient.create<CategoryWriteInput>({
    endpoint: "categories",
    content: buildCategoryContent(input),
  });
};

詳細は省略しますが、この記事のURLなどをプロンプトに渡せば、同様の仕組みは再現できると思います。

運用してみての感想

良かったこと

  • カテゴリーが自動で付与できるようになったので、運用がとても楽になった。特にカラーコードの組み合わせを考える手間がなくなりとても嬉しい

微妙だったこと

  • カテゴリーの精度はまずまずで、やや自分のブログのテイストと合わないカテゴリーも生成されている。自分味のあるカテゴリーをあらかじめ用意してから運用し、プロンプトも改善すると精度も自分好みになるかも知れない。
  • プレビューでWRITE APIを叩いて、管理画面に戻ると管理画面のクライアントキャッシュが更新されていないので誤更新のリスクがある。確実に反映させるためには必ずリロードを挟まないといけない(これで数回更新をロールバックしてしまった)。

このようにmicroCMS本体に改善を入れないといけない部分がありつつも、当初の課題となっていた運用面は改善されコンテンツを書くことに集中できるようになったのでとても良かったと思います。

microCMS内にもこのような機能があると嬉しいなと思いつつ、CMSというコンテンツ運用の箱ではAIを入れることによる副作用も考えられると思うので、このような外部からのアプローチというのは検討の余地があると感じました。

プレビューで生成するということは実際のコンテンツの見え方を確認しながら更新するというヘッドレスCMSのデメリットを解決できる可能性もあると思うので、コンテンツ内容の生成やそのほかのメタデータの運用などもプレビュー内からアプローチできないか今後も検討したいと思いました。

実運用という観点ではリアルタイム反映のような仕組みは欲しいなと思います。