メインコンテンツへスキップ
kt-tech.blog

【移行ガイド】microCMSからNotion APIへブログCMSを完全移行する

実装7分で読めます

この記事でわかること

  • コンポーネントを触らずに、API クライアントだけを差し替える移行戦略
  • Slug プロパティに旧 ID を持たせて、既存 URL を一つも変えずに移行する方法
  • Notion SDK v5 で databases が dataSources に変わった件
  • ビルド時にレート制限でタイムアウトしたときの逃げ方

Next.js 13(App Router)、Notion API トークン、移行元のヘッドレス CMS が稼働中であること。

概要: microCMSで運用していた技術ブログをNotionに完全移行した記録です。20記事のデータ移行、Notion APIクライアントの実装、ISR対応、URL維持まで、移行の全工程をまとめました。

はじめに

microCMS で運営していた技術ブログ(Next.js 13 App Router)を Notion に完全移行しました。理由は、記事を書く場所を Notion に統一したかったことと、開発セッションを自動記事化する仕組みと連携させたかったことです。ただし既存の URL(/blogs/{microCMS-ID})は維持する必要がありました。


1. 移行計画

全体のアプローチ

既存のmicroCMSのインターフェース(getList, getDetail, getCategoryList等)を維持したまま、バックエンドだけNotionに差し替える戦略を採用。

microCMS API ──→ libs/microcms.ts ──→ コンポーネント
                      ↓ 置換
Notion API   ──→ libs/notion.ts   ──→ コンポーネント(変更なし)

Notion DBの設計

Tech Articles Databaseに以下のプロパティを追加:

プロパティ 用途
Slug rich_text microCMS IDを保持(URL維持)
Eyecatch url アイキャッチ画像URL
Source select microCMS / Notion(データ元の識別)

既存プロパティ: Title, Category(select), Tags(multi_select), Status(select), Created(date)

2. データ移行

microCMSからのエクスポート

microCMS JS SDKで全記事を取得し、JSONファイルにエクスポート。

JavaScript
const [blogs, categories, tags] = await Promise.all([
  client.getList({ endpoint: 'blogs', queries: { limit: 100 } }),
  client.getList({ endpoint: 'categories', queries: { limit: 100 } }),
  client.getList({ endpoint: 'tags', queries: { limit: 100 } }),
]);

Notionへのインポート

Notion MCP(Model Context Protocol)経由でページを一括作成。各記事のプロパティとMarkdown本文をそのまま移行。

重要ポイント:

  • SlugにmicroCMSのIDを設定 → URLが変わらない
  • Source: "microCMS"で移行元を識別
  • カバー画像にeyecatch画像を設定
  • タグのマッピング(例: microCMSの"Zustand" → Notionの"zustand")

3. Notion APIクライアント実装

SDK v5の変更点

@notionhq/client v5ではdatabases.queryが廃止され、dataSources.queryに変更。

TypeScript
// v4以前
const response = await notion.databases.query({ database_id: DB_ID });

// v5
const response = await notion.dataSources.query({ data_source_id: DS_ID });

型定義の互換性維持

microCMSの型(Blog, Tag, Category)をそのまま維持し、Notionのページデータから変換。

TypeScript
export type Blog = {
  id: string;      // Slug(URLパス用)
  title: string;
  body: string;    // Blocks → Markdown変換
  eyecatch?: { url: string };
  category?: Category;
  tags?: Tag[];
  createdAt: string;
  // ...MicroCMSDate互換フィールド
};

Blocks → Markdown変換

Notion APIのブロックデータをMarkdownに変換するblocksToMarkdown関数を実装。heading, paragraph, code, image, list, table, bookmark, callout等に対応。

4. ISRでのレート制限対策

問題

全48記事の静的生成時にNotion APIのレート制限(3リクエスト/秒)に引っかかり、ビルドがタイムアウト。

解決策

  1. generateStaticParamsを空配列に — ビルド時にページを生成しない
  2. revalidate = 3600 — ISRで初回アクセス時に生成、1時間キャッシュ
  3. リクエスト内のメモ化 — 同じリクエストの中で同じ API を何度も叩かないようにする
重要
3 のメモ化は React の cache() を使ってリクエスト単位に閉じること。モジュールスコープの変数に貯めると、Workers や Lambda ではインスタンスが使い回されて古い一覧が返り続けます(新記事を公開しても出てこない)。筆者はこれで一度踏んでいます。
TypeScript
export const revalidate = 3600;
export const dynamicParams = true;

export async function generateStaticParams() {
  return []; // ビルド時には生成しない
}

5. URL維持の仕組み

Slugプロパティの活用

getDetailではまずSlugプロパティで検索し、見つからなければNotion Page IDで検索するフォールバック。

TypeScript
export const getDetail = async (slug: string) => {
  const response = await notion.dataSources.query({
    data_source_id: DATABASE_ID,
    filter: { property: 'Slug', rich_text: { equals: slug } },
  });
  if (response.results.length === 0) {
    const page = await notion.pages.retrieve({ page_id: slug });
    return pageToBlog(page, true);
  }
  return pageToBlog(response.results[0], true);
};

Tips

Tips
Notion SDK v5 では databases のメソッドが dataSources に移動。パラメータ名も database_iddata_source_id に変更されているので注意
Tips
encodeURIComponent で Slug を生成すると、日本語カテゴリ名も URL セーフに扱える
Tips
Notion の multi_select に存在しないタグを指定するとエラーになる。事前に DB スキーマを更新してタグを追加しておく
Tips
.next キャッシュを削除しないと dev サーバーが古いデータを返すことがある。rm -rf .next で解決

参考リンク

まとめ

  • microCMSからNotionへの移行は、APIクライアントの差し替えが中心で、コンポーネント側の変更は最小限
  • Notion SDK v5ではdataSources.queryを使う(databases.queryは廃止)
  • SlugプロパティでmicroCMSのIDを保持することで、既存URLを完全に維持
  • ISR とリクエスト内のメモ化でNotion API のレート制限を回避(モジュールスコープには貯めない)
  • 移行後はNotionで記事管理が一元化され、運用が大幅にシンプルに

更新履歴

  1. 「リスト取得にメモリキャッシュ」の記述を、リクエスト内のメモ化に訂正し、モジュールスコープに貯めると isolate の使い回しで古い一覧が返る事故を警告として追加。壊れていた目次マーカーを自動目次に差し替え。コードブロックの言語指定を javascript から text に修正。引用形式の Tips を callout に統一。参考リンクの URL 重複表記をタイトル付きに整理し、関連記事を追加。箸条書きだった「はじめに」を文章に整理。「この記事でわかること」「対象読者」「前提条件」を概要プロパティへ移動。

この記事のタグ