📌概要: microCMSで運用していた技術ブログをNotionに完全移行した記録です。20記事のデータ移行、Notion APIクライアントの実装、ISR対応、URL維持まで、移行の全工程をまとめました。
[toc]
はじめに
-
microCMSで運営していた技術ブログ(Next.js 13 App Router)をNotionに完全移行した
-
理由: Notionで記事を書く運用に統一したかった、session-to-notionスキルとの連携
-
既存のURL(/blogs/{microCMS-ID})を維持しつつ移行する必要があった
この記事でわかること
-
microCMSからNotionへのデータ移行手順
-
Notion APIクライアントの実装(microCMS互換インターフェース)
-
Notion SDK v5の新API(dataSources.query)の使い方
-
ISR(Incremental Static Regeneration)でのNotion API レート制限対策
-
SlugプロパティによるURL維持の仕組み
対象読者
-
microCMSや他のヘッドレスCMSからNotionに乗り換えたい方
-
Notion APIを使ったブログシステムに興味がある方
-
Next.js App Routerでの動的データソース切り替えを検討している方
前提条件
-
Next.js 13(App Router)
-
Notion APIトークン取得済み
-
既存のmicroCMSブログが稼働中
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ファイルにエクスポート。
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に変更。
const response = await notion.databases.query({ database_id: DB_ID });
const response = await notion.dataSources.query({ data_source_id: DS_ID });
型定義の互換性維持
microCMSの型(Blog, Tag, Category)をそのまま維持し、Notionのページデータから変換。
export type Blog = {
id: string;
title: string;
body: string;
eyecatch?: { url: string };
category?: Category;
tags?: Tag[];
createdAt: string;
};
Blocks → Markdown変換
Notion APIのブロックデータをMarkdownに変換するblocksToMarkdown関数を実装。heading, paragraph, code, image, list, table, bookmark, callout等に対応。
4. ISRでのレート制限対策
問題
全48記事の静的生成時にNotion APIのレート制限(3リクエスト/秒)に引っかかり、ビルドがタイムアウト。
解決策
-
generateStaticParamsを空配列に — ビルド時にページを生成しない
-
revalidate = 3600 — ISRで初回アクセス時に生成、1時間キャッシュ
-
リスト取得にメモリキャッシュ — 同一ビルド内でのAPI呼び出しを削減
export const revalidate = 3600;
export const dynamicParams = true;
export async function generateStaticParams() {
return [];
}
5. URL維持の仕組み
Slugプロパティの活用
getDetailではまずSlugプロパティで検索し、見つからなければNotion Page IDで検索するフォールバック。
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
📌Tip 1: Notion SDK v5ではdatabasesのメソッドがdataSourcesに移動。パラメータ名もdatabase_id→data_source_idに変更されているので注意
📌Tip 2: encodeURIComponentでSlugを生成すると、日本語カテゴリ名もURLセーフに扱える
📌Tip 3: Notionのmulti_selectに存在しないタグを指定するとエラーになる。事前にDBスキーマを更新してタグを追加しておく
📌Tip 4: .nextキャッシュを削除しないとdevサーバーが古いデータを返すことがある。rm -rf .nextで解決
参考リンク
まとめ
-
microCMSからNotionへの移行は、APIクライアントの差し替えが中心で、コンポーネント側の変更は最小限
-
Notion SDK v5ではdataSources.queryを使う(databases.queryは廃止)
-
SlugプロパティでmicroCMSのIDを保持することで、既存URLを完全に維持
-
ISR + メモリキャッシュでNotion APIのレート制限を回避
-
移行後はNotionで記事管理が一元化され、運用が大幅にシンプルに