

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ブログが稼働中
既存のmicroCMSのインターフェース(getList, getDetail, getCategoryList等)を維持したまま、バックエンドだけNotionに差し替える戦略を採用。
microCMS API ──→ libs/microcms.ts ──→ コンポーネント
↓ 置換
Notion API ──→ libs/notion.ts ──→ コンポーネント(変更なし)
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)
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 MCP(Model Context Protocol)経由でページを一括作成。各記事のプロパティとMarkdown本文をそのまま移行。
重要ポイント:
SlugにmicroCMSのIDを設定 → URLが変わらない
Source: "microCMS"で移行元を識別
カバー画像にeyecatch画像を設定
タグのマッピング(例: microCMSの"Zustand" → Notionの"zustand")
@notionhq/client v5ではdatabases.queryが廃止され、dataSources.queryに変更。
// 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のページデータから変換。
export type Blog = {
id: string; // Slug(URLパス用)
title: string;
body: string; // Blocks → Markdown変換
eyecatch?: { url: string };
category?: Category;
tags?: Tag[];
createdAt: string;
// ...MicroCMSDate互換フィールド
};
Notion APIのブロックデータをMarkdownに変換するblocksToMarkdown関数を実装。heading, paragraph, code, image, list, table, bookmark, callout等に対応。
全48記事の静的生成時にNotion APIのレート制限(3リクエスト/秒)に引っかかり、ビルドがタイムアウト。
generateStaticParamsを空配列に — ビルド時にページを生成しない
revalidate = 3600 — ISRで初回アクセス時に生成、1時間キャッシュ
リスト取得にメモリキャッシュ — 同一ビルド内でのAPI呼び出しを削減
export const revalidate = 3600;
export const dynamicParams = true;
export async function generateStaticParams() {
return []; // ビルド時には生成しない
}
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);
};
databasesのメソッドがdataSourcesに移動。パラメータ名もdatabase_id→data_source_idに変更されているので注意encodeURIComponentでSlugを生成すると、日本語カテゴリ名もURLセーフに扱える.nextキャッシュを削除しないとdevサーバーが古いデータを返すことがある。rm -rf .nextで解決Notion API公式ドキュメント: https://developers.notion.com
@notionhq/client npm: https://www.npmjs.com/package/@notionhq/client
Next.js ISR: https://nextjs.org/docs/app/building-your-application/data-fetching/incremental-static-regeneration
microCMSからNotionへの移行は、APIクライアントの差し替えが中心で、コンポーネント側の変更は最小限
Notion SDK v5ではdataSources.queryを使う(databases.queryは廃止)
SlugプロパティでmicroCMSのIDを保持することで、既存URLを完全に維持
ISR + メモリキャッシュでNotion APIのレート制限を回避
移行後はNotionで記事管理が一元化され、運用が大幅にシンプルに