@notion-headless-cms/react-renderer
v0.1.12
Published
Notion ブロックを React コンポーネントとして直接描画するレンダラ。shadcn/ui + Tailwind v4 ベース。
Maintainers
Readme
@notion-headless-cms/react-renderer
Notion API のブロックレスポンスを React コンポーネントとして直接描画するレンダラ。
notion-to-md / Markdown 経由ではなく、BlockObjectResponse を 1:1 で React ツリーに変換する。
UI プリミティブは shadcn/ui (new-york style) 由来、スタイルは Tailwind v4 ユーティリティクラスで完結する。
インストール
pnpm add @notion-headless-cms/react-renderer @notion-headless-cms/notion-orm @notionhq/client react react-dom利用側プロジェクトに Tailwind v4 のセットアップが必須。tailwind.config で本パッケージのソースをスキャン対象に含める:
// tailwind.config.ts (Tailwind v4 では @source を使う方式でも可)
export default {
content: [
"./src/**/*.{ts,tsx}",
"./node_modules/@notion-headless-cms/react-renderer/dist/**/*.{js,mjs}",
],
};使い方
import { Client } from "@notionhq/client";
import { fetchBlockTree } from "@notion-headless-cms/notion-orm";
import { NotionRenderer } from "@notion-headless-cms/react-renderer";
const client = new Client({ auth: process.env.NOTION_TOKEN });
const blocks = await fetchBlockTree(client, pageId);
export default function Page() {
return <NotionRenderer blocks={blocks} />;
}@notion-headless-cms/core と組み合わせて使う (推奨)
createClient 経由で取得すると、ブロックツリーが SWR キャッシュに乗り、画像 URL も
cms.cacheImage 経由で永続プロキシ URL に書き換えられる (Notion 署名 URL の失効対策)。
import {
type NotionBlock,
NotionRenderer,
} from "@notion-headless-cms/react-renderer";
// "use client" を含まないサーバーサイド専用エントリ
import { resolveBlockImageUrls } from "@notion-headless-cms/react-renderer/server";
const post = await cms.posts.find(slug);
const notionBlocks =
((await post?.notionBlocks()) as NotionBlock[] | undefined) ?? [];
const blocks = await resolveBlockImageUrls(notionBlocks, cms.cacheImage);
return <NotionRenderer blocks={blocks} />;cms.posts.find(slug).notionBlocks()— ブロックツリーをキャッシュ経由で取得 (DataSource.loadNotionBlocksを実装している場合のみ。@notion-headless-cms/notion-ormは対応済み)resolveBlockImageUrls(blocks, cacheImage)—image/video/audio/file/pdfのfile型 URL をcacheImage(url)で書き換えた新しいツリーを返す。cacheImageがundefinedのときは入力をそのまま返す。external型は触らない。children も再帰的に処理する- このサブパスは React Server Component やサーバーローダから呼び出すために
"use client"を含めていない
コンポーネント差し替え
import { NotionRenderer, type ComponentOverrides } from "@notion-headless-cms/react-renderer";
const components: ComponentOverrides = {
Code: MyCustomCode, // 既定の Code 実装を上書き
};
<NotionRenderer blocks={blocks} components={components} />;数式 (KaTeX) を使う
v0.2 以降、block / inline equation は 既定で動的 import されるため、katex を peer に入れるだけで自動的に整形される(サブパス react-renderer/equation は廃止)。
pnpm add katex利用側プロジェクトの CSS に katex/dist/katex.min.css を読み込むこと(Tailwind v4 なら @import "katex/dist/katex.min.css" を @source より前に置く)。katex は optional peer なので数式を使わない構成では未インストールでよく、equation ブロックが現れた瞬間にだけ chunk が fetch される。
mermaid 図を使う(opt-in)
mermaid は約 1 MB と重く Cloudflare Workers の 3 MiB 上限を圧迫するため、既定では含めず opt-in サブパスで提供する。
pnpm add mermaidimport { NotionRenderer } from "@notion-headless-cms/react-renderer";
import { MermaidCode } from "@notion-headless-cms/react-renderer/mermaid";
<NotionRenderer blocks={blocks} components={{ Code: MermaidCode }} />;MermaidCode は language === "mermaid" のときだけ動的 import で mermaid を読み SVG にレンダし、それ以外の言語は既定の Code に委譲する。
Notion 更新の表示反映 (/router, /next)
Notion のページを編集したあと、開いている画面を静かに最新化するためのフックとコンポーネント。クエリ無し・別 API への fetch 無しで、フレームワーク本来の再評価機構(loader / RSC)だけを使う。
React Router v7
import { NotionRevalidator } from "@notion-headless-cms/react-renderer/router";
export default function Post() {
return (
<article>
<NotionRevalidator />
{/* ... */}
</article>
);
}内部で useRevalidator() を呼び loader を再走させる。サーバ側で cloudflarePreset({ env, ctx }) 等により waitUntil が配線されていれば、前回訪問時の SWR bg 更新で KV が最新化されており、再呼び出しで新内容が返って画面が差し替わる。
Next.js App Router
import { NotionRevalidator } from "@notion-headless-cms/react-renderer/next";
export default async function Page({ params }) {
// ...
return (
<article>
<NotionRevalidator />
{/* ... */}
</article>
);
}内部で useRouter().refresh() を呼び、Server Component を再評価して RSC ストリーム差分で UI を更新する。
オプション
| プロップ / オプション | 既定値 | 説明 |
|---|---|---|
| on | "mount" | 発火タイミング。"mount" / "visibility" / 配列で複数指定可。poll 指定時は既定が [](空)になる |
| poll.url | — | peekVersion を返すエンドポイント URL |
| poll.version | — | ページロード時の item.lastEditedTime(比較基準) |
| poll.intervalMs | 500 | ポーリング間隔(ms) |
| poll.timeoutMs | 30000 | タイムアウト(ms) |
<NotionRevalidator on={["mount", "visibility"]} />
// マウント時 + タブが visible に戻った時の両方で再評価
<NotionRevalidator
poll={{ url: `/api/posts/${item.slug}/check`, version: item.lastEditedTime }}
/>
// KV ポーリング: バックグラウンド SWR 更新の完了を検出してから revalidate
// notionUpdatedAt 変化 → 即 revalidate / cachedAt 変化(確認完了・更新なし)→ 停止フック版 useNotionRevalidate(opts) も同じシグネチャで提供。React 非依存の素 HTML 向けには @notion-headless-cms/core/html の notionRevalidatorScript() を使う。
対応ブロック
paragraph / heading_1-3 / bulleted_list_item / numbered_list_item / to_do / toggle / callout / quote / code / equation / divider / image / video / audio / file / pdf / bookmark / link_preview / link_to_page / child_page / child_database / embed / table / table_row / column_list / column / synced_block / breadcrumb / table_of_contents / unsupported
設計
- 入力は
fetchBlockTreeが返す children を再帰解決済みのツリー - 全コンポーネントに
"use client"ディレクティブが付き、Next.js App Router の server component から<NotionRenderer>を直接呼べる - 連続する
bulleted_list_item/numbered_list_itemは内部で<ul>/<ol>にグループ化される - interactive embed (Twitter widgets / YouTube facade) は副作用を hook で隔離
