Notion
從 Notion 擷取頁面,並以 Fumadocs 算繪。
簡介
@fumadocs/notion 可將 Notion 資料庫轉換為 Fumadocs 內容來源。它提供一個動態內容來源,透過官方 Notion API 擷取頁面;另外也提供伺服器端算繪器,以 Fumadocs UI 算繪 Notion 區塊。
它不包含用戶端算繪器,也不會將內容轉換為 MDX。區塊一律在伺服器端算繪,因此不需要打包工具,也沒有型別產生步驟。
設定
請安裝此整合以及官方的 Notion 用戶端:
npm install @fumadocs/notion @notionhq/client請在 Fumadocs UI 樣式之後,匯入算繪器的 Tailwind CSS 來源:
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
@import '@fumadocs/notion/css/preset.css';Notion 資料庫
- 建立 Notion 整合,並授予讀取內容的權限。
- 將存放您頁面的資料庫連接至該整合。
- 將整合權杖與資料庫的 data source ID 存放在僅限伺服器端存取的環境變數中。
NOTION_TOKEN=ntn_...
NOTION_DATA_SOURCE_ID=...請使用資料來源 (data source) ID,而非資料庫 URL 中顯示的資料庫 ID。在目前的 Notion API 中,資料庫是一個容器, 每次查詢的對象都是其中一個資料來源。詳情請參閱 Notion 的資料來源升級指南。
Notion 整合
整合只需建立一次,並由內容來源與檔案路由共用。
import { Client } from '@notionhq/client';
import { createNotion } from '@fumadocs/notion';
export const notion = createNotion({
client: new Client({ auth: process.env.NOTION_TOKEN }),
dataSourceId: process.env.NOTION_DATA_SOURCE_ID!,
});內容載入器
將 notion.dynamicSource() 傳入 dynamicLoader(),並透過 getSource() 對外提供該載入器:
import { dynamicLoader } from 'fumadocs-core/source';
import { notion } from '@/lib/notion';
const source = dynamicLoader(
notion.dynamicSource({
// 將 Notion 屬性對應到頁面欄位
properties: {
title: 'Name',
slug: 'Slug',
description: 'Description',
},
// 篩選、排序或以其他方式自訂查詢條件
query: {
filter: {
property: 'Published',
checkbox: { equals: true },
},
},
}),
{
baseUrl: '/docs',
},
);
export async function getSource() {
return source.get();
}來源會分頁走訪所有符合條件的 data source 資料列,並延遲載入每個頁面的巢狀區塊。在載入器失效之前,重複載入同一頁面內容的呼叫都會經過去重處理。
算繪頁面
在版面配置中使用載入器建立頁面樹。載入頁面的區塊後,再以 NotionRenderer 算繪;目錄則由 blocksToTableOfContents() 產生:
import type { ReactNode } from 'react';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { getSource } from '@/lib/source';
export default async function Layout({ children }: { children: ReactNode }) {
const source = await getSource();
return <DocsLayout tree={source.getPageTree()}>{children}</DocsLayout>;
}Notion 檔案
Notion 託管的檔案 URL (圖片、影片、PDF) 大約一小時後就會失效,因此無法直接嵌入經過快取或以靜態方式提供的頁面。
createNotionFileUrlResolver() 會將這些內部資源轉交給檔案處理器,由它隨需取得新的簽署 URL。請使用 createNotionFileHandler() 掛載處理器,它會先驗證所請求的區塊是否屬於您資料來源中的頁面,再進行重新導向:
import { createNotionFileHandler } from '@fumadocs/notion';
import { notion } from '@/lib/notion';
export const GET = createNotionFileHandler(notion);預設路由為 /api/notion/file。若要掛載到其他位置,請將相同的 path 傳入這兩個函式:
createNotionFileUrlResolver({ pageId: page.data.id, path: '/notion/file' });若要進行靜態匯出,請為您的資源使用可長期存取的外部 URL,並省略 getFileUrl,算繪器便會
直接連結 Notion 的 URL。檔案處理器需要在動態執行階段環境中運作。
完成
將頁面新增至資料庫並標記為已發布後,這些頁面就會顯示在 docs 路由下。
重新驗證
載入器會快取頁面,直到您使快取失效為止。若要同步 Notion 上的編輯內容,請在受信任的 webhook 或重新驗證路由中呼叫 revalidate():
export function revalidateNotion() {
return source.revalidate();
}若您需要類似預覽模式的設定,讓每個請求都能反映最新內容,請在讀取前先將快取失效:
export async function getSource() {
source.invalidate();
return source.get();
}頁面屬性
properties 會將 Notion 屬性對應到頁面欄位。若省略此設定,來源將會:
- 使用第一個 Notion
title屬性作為頁面標題; - 使用名為
Slug的屬性作為 URL,若沒有此屬性,則改用依標題產生的 slug; - 若有名為
Description的屬性,則使用該屬性作為描述。
slug 可包含 /,以建立巢狀路由。若虛擬路徑重複或不安全,會直接拋出說明明確的錯誤,而不會在未提示的情況下覆蓋既有頁面。
查詢與路徑
請透過 query 傳入 Notion 的篩選與排序條件。您可以使用 baseDir 將虛擬頁面放在指定目錄下,或使用 generatePath 完整自訂虛擬檔案路徑:
notion.dynamicSource({
baseDir: 'docs',
query: {
sorts: [{ timestamp: 'last_edited_time', direction: 'descending' }],
},
generatePath(page, info) {
return info.slugs.length > 0 ? `${info.slugs.join('/')}.mdx` : `${page.id}.mdx`;
},
});自訂算繪
NotionRenderer 內建常見區塊類型的算繪器。您可以傳入 components 來新增或覆寫這些算繪器,每個覆寫的型別都會對應到其區塊類型:
import { NotionRenderer } from '@fumadocs/notion';
<NotionRenderer
blocks={blocks}
components={{
callout({ block, children, renderRichText }) {
return (
<aside className="rounded-lg bg-fd-muted p-4">
{renderRichText(block.callout.rich_text)}
{children}
</aside>
);
},
}}
/>;程式碼區塊預設會在伺服器端透過 Shiki 進行語法突顯。傳入 highlightCode={false} 即可算繪未經語法突顯的程式碼;若要改用您自己的語法突顯器,則可傳入一個函式。
僅匯入來源
若程式碼只需要擷取 Notion 頁面 (例如搜尋索引器或 llms.txt 路由) ,可以改從 @fumadocs/notion/source 匯入,以略過 React 算繪器的進入點:
import { createNotion } from '@fumadocs/notion/source';此進入點會將 Notion SDK 保留為外部相依套件,且不含任何 React 匯入。此處同樣提供 blocksToPlainText()、blocksToStructuredData() 與 blocksToTableOfContents()。
How is this guide?
最後更新於
