跳至內容
Fumadocs

Notion

從 Notion 擷取頁面,並以 Fumadocs 算繪。

簡介

@fumadocs/notion 可將 Notion 資料庫轉換為 Fumadocs 內容來源。它提供一個動態內容來源,透過官方 Notion API 擷取頁面;另外也提供伺服器端算繪器,以 Fumadocs UI 算繪 Notion 區塊。

它不包含用戶端算繪器,也不會將內容轉換為 MDX。區塊一律在伺服器端算繪,因此不需要打包工具,也沒有型別產生步驟。

設定

請安裝此整合以及官方的 Notion 用戶端:

npm install @fumadocs/notion @notionhq/client

請在 Fumadocs UI 樣式之後,匯入算繪器的 Tailwind CSS 來源:

Tailwind CSS
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
@import '@fumadocs/notion/css/preset.css';

Notion 資料庫

  1. 建立 Notion 整合,並授予讀取內容的權限。
  2. 將存放您頁面的資料庫連接至該整合。
  3. 將整合權杖與資料庫的 data source ID 存放在僅限伺服器端存取的環境變數中。
.env.local
NOTION_TOKEN=ntn_...
NOTION_DATA_SOURCE_ID=...

請使用資料來源 (data source) ID,而非資料庫 URL 中顯示的資料庫 ID。在目前的 Notion API 中,資料庫是一個容器, 每次查詢的對象都是其中一個資料來源。詳情請參閱 Notion 的資料來源升級指南。

Notion 整合

整合只需建立一次,並由內容來源與檔案路由共用。

lib/notion.ts
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() 對外提供該載入器:

lib/source.ts
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() 掛載處理器,它會先驗證所請求的區塊是否屬於您資料來源中的頁面,再進行重新導向:

src/pages/_api/api/notion/file.ts
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():

lib/source.ts
export function revalidateNotion() {
  return source.revalidate();
}

若您需要類似預覽模式的設定,讓每個請求都能反映最新內容,請在讀取前先將快取失效:

lib/source.ts
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?

最後更新於