—
Notionを最強のヘッドレスCMSへ変貌させる:Next.js ISRとAPIレートリミットを制する極限のアーキテクチャ
「ドキュメント管理はNotionだが、ブログや製品ドキュメントの公開用CMSはContentfulやMicroCMSを別途導入する」……そんな二重管理による情報のサイロ化に、君は疲弊していないか?
真のエンジニアリング・エクセレンスとは、「情報の入力源(Single Source of Truth)」を一本化し、運用の摩擦をゼロにすることにある。Notionは、非エンジニアの編集者にとって最高のUIでありながら、エンジニアにとっては強力な構造化データベースだ。
しかし、NotionをヘッドレスCMSとして実戦投入するには、避けては通れない「3リクエスト/秒」というあまりに貧弱なAPIレートリミットの壁がある。本稿では、Next.jsのISR(Incremental Static Regeneration)を駆使し、この制約を突破して爆速のサイトパフォーマンスを実現する、現場直結の知見を叩き込む。
—
1. アーキテクチャの全貌:Notionを「書き込み専用」と割り切る
Notion APIをランタイム(ブラウザからのリクエスト時)に叩くのは素人の仕事だ。ユーザーがサイトを訪れるたびにAPIを呼び出せば、瞬時にレートリミットに達し、サイトは沈黙する。
「NotionはDBであり、Vercel Edge Networkがキャッシュ層である」という設計思想を持て。
推奨スタック
- Backend: Notion Database (Status, Tags, Published Dateの管理)
- Frontend: Next.js (App Router or Pages Router)
- Deployment: Vercel
- Communication: `@notionhq/client` + `notion-client` (高速なレンダリングのため)
—
2. APIレートリミット(3req/s)の死線を越えるキャッシュ戦略
Notion APIの最大の弱点は、1ページ取得するのにもプロパティ取得、ブロックリスト取得……と複数のリクエストを要する点だ。これを解決するには、ビルド時およびISR実行時にデータを「絞り取る」戦略が必要だ。
実装の要諦:並列リクエストの制御
大量のページをビルドする際、`Promise.all`で一気にリクエストを投げると、Notion APIは即座に 429 (Too Many Requests) を返す。これを防ぐために、リミッターを実装したフェッチ関数を定義せよ。
// lib/notion.ts
import { Client } from ‘@notionhq/client’;
import pLimit from ‘p-limit’; // 並列実行数を制限するライブラリ
const notion = new Client({ auth: process.env.NOTION_TOKEN });
const limit = pLimit(2); // 同時実行を2に制限し、余裕を持たせる
export const getPageData = async (pageId: string) => {
return limit(() => notion.pages.retrieve({ page_id: pageId }));
};
—
3. ISRによる「鮮度」と「速度」の両立
ISRは、ビルド後にバックグラウンドでページを再生成するNext.jsの至宝だ。これにより、Notion側で記事を更新した後、数秒〜数分で変更を反映させつつ、ユーザーには常にエッジキャッシュから静的ファイルを返すことができる。
実践:getStaticPropsでの実装(Pages Router例)
App Routerの場合は `revalidate = 60` のようなSegment Configを使用する。
// pages/posts/[slug].tsx
export const getStaticProps: GetStaticProps = async ({ params }) => {
const page = await getPageBySlug(params.slug); // 独自実装のページ取得関数
if (!page) return { notFound: true };
return {
props: {
post: page,
// 60秒ごとにバックグラウンドで再生成を試みる
// ユーザーのアクセスをトリガーに、Notion APIを1回だけ叩きに行く
revalidate: 60,
},
};
};
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await getAllPublishedPosts();
return {
// 初回ビルド時に含めるページ。それ以外は初回アクセス時に生成(fallback: ‘blocking’)
paths: posts.map((p) => ({ params: { slug: p.slug } })),
fallback: ‘blocking’,
};
};
—
4. 開発効率を極限まで高める「隠れた知法」
ツールを使いこなす者は、マウスを触る時間を削る。
Notion 爆速ショートカット
- `Cmd + Option + 1`:見出し1(構造化の基本)
- `Cmd + L`:ページのURLをコピー(APIテスト時にIDを抜くのに必須)
- `Cmd + J`:Notionのクイック検索(複数DB間の往来に)
- `/code`:コードブロック即出し(言語指定は `typescript` を標準化せよ)
神プラグイン & ツール
- Notion to Markdown (notion-to-md): Notionのブロック構造をクリーンなMarkdownに変換する。自前でパーサーを書く時間は、ビジネスロジックに充てるべきだ。
- Save to Notion (Chrome Extension): 参考資料を特定のDBフォーマットで一瞬で保存。情報のサイロ化を防ぐ一歩。
—
5. チーム開発での設定共有化ルール(JSON構成案)
NotionのDBスキーマが変わると、Next.js側で型エラーが発生する。これを防ぐために、プロパティ名を共通定数として管理し、チームで共有せよ。
// constants/notion-schema.json
{
“DB_ID”: “xxxxxxxxxxxxxxxxxxxxxxxx”,
“PROPERTIES”: {
“TITLE”: “Name”,
“SLUG”: “Slug”,
“STATUS”: “Status”,
“PUBLISHED_AT”: “PublishedDate”,
“TAGS”: “Tags”
},
“STATUS_VALUES”: {
“PUBLISHED”: “Published”,
“DRAFT”: “Draft”,
“ARCHIVED”: “Archived”
}
}
このJSONを元に型定義を生成することで、「Notion側のカラム名を変えたらサイトが壊れた」という事故を未然に防ぐ。
—
結言:ナレッジの流動性を最大化せよ
Notion × Next.js ISRの真の価値は、「書く」という行為と「届ける」という行為の距離をゼロにすることにある。エンジニアがいちいちMarkdownをコミットし、PRをマージし、デプロイを待つ必要はない。
編集者がNotionで「公開」ステータスに切り替えた瞬間、VercelのISRが静かに、かつ確実に世界を書き換える。この「情報の高純度なパイプライン」を構築することこそが、モダンな開発チームにおけるテックリードの使命である。
君のチームのベロシティは、ドキュメントの置き場所一つで変わる。今すぐ、そのNotionをただのメモ帳から、最強のコンテンツ・エンジンへと昇華させよう。