要將 Notion 資料庫整合到 Next.js 專案中,您可以使用 Notion 作為內容管理系統 (CMS) 並將其內容顯示在您的網站上。以下是一個簡單的逐步指南,可協助您將 Notion 資料庫整合到 Next.js 中。
還祭祀的可以看上篇文章:【01.【個人網站】如何利用Notion資料庫作為全端開發】
首先,您需要安裝Notion的官方SDK@notionhq/client,以與Notion API進行通訊。您可以使用npm或yarn安裝它:
npm install @notionhq/client # or yarn add @notionhq/client
在 Next.js 專案的根目錄中建立檔案 lib/notion.js 並如下設定 Notion 用戶端:
// lib/notion.js import { Client } from '@notionhq/client'; const notion = new Client({ auth: process.env.NOTION_API_KEY, }); export const getDatabase = async (databaseId) => { const response = await notion.databases.query({ database_id: databaseId }); return response.results; };
確保將您的 Notion API 金鑰儲存在環境變數 NOTION_API_KEY 中。 (.env.local)
NOTION_API_KEY=your_secret_api_key
在 Next.js 頁面中,您可以使用 getStaticProps 或 getServerSideProps 從 Notion 資料庫中取得內容。
// pages/index.js import { getDatabase } from '../lib/notion'; export const getStaticProps = async () => { const databaseId = process.env.NOTION_DATABASE_ID; const posts = await getDatabase(databaseId); return { props: { posts, }, revalidate: 1, // ISR (Incremental Static Regeneration) }; }; export default function Home({ posts }) { return ( <div> <h1>My Notion Blog</h1> <ul> {posts.map((post) => ( <li key={post.id}> {post.properties.Name.title[0].plain_text} </li> ))} </ul> </div> ); }
確保您的 Notion 資料庫 ID 會作為環境變數儲存在 NOTION_DATABASE_ID 中。
NOTION_DATABASE_ID=your_database_id
最後,將您的 Next.js 專案部署到 Vercel 或其他平台,並驗證您是否可以成功從 Notion 資料庫取得並顯示資料。
其他提示
按照以下步驟,您可以成功地將 Notion 資料庫整合到您的 Next.js 專案中,並使用它來管理和顯示內容。
最後,為了方便與react-notion-x組件集成,我最終使用了這種方法。
確保將您的 Notion 資料庫 ID 和 Notion API 金鑰新增至 .env.local 檔案:
NOTION_DATABASE_ID=your_database_id NOTION_API_KEY=your_secret_api_key
首先,安裝必要的依賴項,例如 notion-types、notion-utils,並處理對 Notion API 的請求。
npm install notion-types notion-utils got p-map
在您的 Next.js 專案中,建立一個文件,例如 lib/NotionAPI.ts,它將封裝與 Notion API 的交互。該檔案將包含呼叫 Notion API 端點以從頁面和集合中獲取資料的方法。
// lib/NotionAPI.ts import * as notion from "notion-types"; import got, { OptionsOfJSONResponseBody } from "got"; import { getBlockCollectionId, getPageContentBlockIds, parsePageId, uuidToId, } from "notion-utils"; import pMap from "p-map"; // 定义权限记录接口 export interface SignedUrlRequest { permissionRecord: PermissionRecord; url: string; } export interface PermissionRecord { table: string; id: notion.ID; } export interface SignedUrlResponse { signedUrls: string[]; } // 定义NotionAPI类 export class NotionAPI { private readonly _apiBaseUrl: string; private readonly _authToken?: string; private readonly _activeUser?: string; private readonly _userTimeZone: string; constructor({ apiBaseUrl = "<https://www.notion.so/api/v3>", authToken, activeUser, userTimeZone = "America/New_York", }: { apiBaseUrl?: string; authToken?: string; userLocale?: string; userTimeZone?: string; activeUser?: string; } = {}) { this._apiBaseUrl = apiBaseUrl; this._authToken = authToken; this._activeUser = activeUser; this._userTimeZone = userTimeZone; } // 获取页面内容 public async getPage( pageId: string, { concurrency = 3, fetchMissingBlocks = true, fetchCollections = true, signFileUrls = true, chunkLimit = 100, chunkNumber = 0, gotOptions, }: { concurrency?: number; fetchMissingBlocks?: boolean; fetchCollections?: boolean; signFileUrls?: boolean; chunkLimit?: number; chunkNumber?: number; gotOptions?: OptionsOfJSONResponseBody; } = {} ): Promise<notion.ExtendedRecordMap> { const page = await this.getPageRaw(pageId, { chunkLimit, chunkNumber, gotOptions, }); const recordMap = page?.recordMap as notion.ExtendedRecordMap; if (!recordMap?.block) { throw new Error(`Notion page not found "${uuidToId(pageId)}"`); } recordMap.collection = recordMap.collection ?? {}; recordMap.collection_view = recordMap.collection_view ?? {}; recordMap.notion_user = recordMap.notion_user ?? {}; recordMap.collection_query = {}; recordMap.signed_urls = {}; if (fetchMissingBlocks) { while (true) { const pendingBlockIds = getPageContentBlockIds(recordMap).filter( (id) => !recordMap.block[id] ); if (!pendingBlockIds.length) { break; } const newBlocks = await this.getBlocks( pendingBlockIds, gotOptions ).then((res) => res.recordMap.block); recordMap.block = { ...recordMap.block, ...newBlocks }; } } const contentBlockIds = getPageContentBlockIds(recordMap); if (fetchCollections) { const allCollectionInstances: Array<{ collectionId: string; collectionViewId: string; }> = contentBlockIds.flatMap((blockId) => { const block = recordMap.block[blockId].value; const collectionId = block && (block.type === "collection_view" || block.type === "collection_view_page") && getBlockCollectionId(block, recordMap); if (collectionId) { return block.view_ids?.map((collectionViewId) => ({ collectionId, collectionViewId, })); } else { return []; } }); await pMap( allCollectionInstances, async (collectionInstance) => { const { collectionId, collectionViewId } = collectionInstance; const collectionView = recordMap.collection_view[collectionViewId]?.value; try { const collectionData = await this.getCollectionData( collectionId, collectionViewId, collectionView, { gotOptions, } ); recordMap.block = { ...recordMap.block, ...collectionData.recordMap.block, }; recordMap.collection = { ...recordMap.collection, ...collectionData.recordMap.collection, }; recordMap.collection_view = { ...recordMap.collection_view, ...collectionData.recordMap.collection_view, }; recordMap.notion_user = { ...recordMap.notion_user, ...collectionData.recordMap.notion_user, }; recordMap.collection_query![collectionId] = { ...recordMap.collection_query![collectionId], [collectionViewId]: (collectionData.result as any) ?.reducerResults, }; } catch (err: any) { console.warn( "NotionAPI collectionQuery error", pageId, err.message ); } }, { concurrency, } ); } if (signFileUrls) { await this.addSignedUrls({ recordMap, contentBlockIds, gotOptions }); } return recordMap; } public async addSignedUrls({ recordMap, contentBlockIds, gotOptions = {}, }: { recordMap: notion.ExtendedRecordMap; contentBlockIds?: string[]; gotOptions?: OptionsOfJSONResponseBody; }) { recordMap.signed_urls = {}; if (!contentBlockIds) { contentBlockIds = getPageContentBlockIds(recordMap); } const allFileInstances = contentBlockIds.flatMap((blockId) => { const block = recordMap.block[blockId]?.value; if ( block && (block.type === "pdf" || block.type === "audio" || (block.type === "image" && block.file_ids?.length) || block.type === "video" || block.type === "file" || block.type === "page") ) { const source = block.type === "page" ? block.format?.page_cover : block.properties?.source?.[0]?.[0]; if (source) { if (!source.includes("secure.notion-static.com")) { return []; } return { permissionRecord: { table: "block", id: block.id, }, url: source, }; } } return []; }); if (allFileInstances.length > 0) { try { const { signedUrls } = await this.getSignedFileUrls( allFileInstances, gotOptions ); if (signedUrls.length === allFileInstances.length) { for (let i = 0; i < allFileInstances.length; ++i) { const file = allFileInstances[i]; const signedUrl = signedUrls[i]; recordMap.signed_urls[file.permissionRecord.id] = signedUrl; } } } catch (err) { console.warn("NotionAPI getSignedfileUrls error", err); } } } public async getPageRaw( pageId: string, { gotOptions, chunkLimit = 100, chunkNumber = 0, }: { chunkLimit?: number; chunkNumber?: number; gotOptions?: OptionsOfJSONResponseBody; } = {} ): Promise<notion.PageChunk> { const parsedPageId = parsePageId(pageId); if (!parsedPageId) { throw new Error(`invalid notion pageId "${pageId}"`); } const body = { pageId: parsedPageId, limit: chunkLimit, chunkNumber: chunkNumber, cursor: { stack: [] }, verticalColumns: false, }; return this.fetch<notion.PageChunk>({ endpoint: "loadPageChunk", body, gotOptions, }); } public async getCollectionData( collectionId: string, collectionViewId: string, collectionView?: any, { limit = 9999, searchQuery = "", userTimeZone = this._userTimeZone, loadContentCover = true, gotOptions, }: { limit?: number; searchQuery?: string; userTimeZone?: string; loadContentCover?: boolean; gotOptions?: OptionsOfJSONResponseBody; } = {} ) { const type = collectionView?.type; const isBoardType = type === "board"; const groupBy = isBoardType ? collectionView?.format?.board_columns_by : collectionView?.format?.collection_group_by; let filters = []; if (collectionView?.format?.property_filters) { filters = collectionView.format?.property_filters.map( (filterObj : any) => ({ property: filterObj?.property, filter: { operator: "and", filters: filterObj?.filter?.filters, }, }) ); } const body = { collection: { id: collectionId, }, collectionView: { id: collectionViewId, }, loader: { type: "reducer", reducers: { collection_group_results: { type: "results", limit, loadContentCover, }, }, userTimeZone, limit, loadContentCover, searchQuery, userLocale: "en", ...(filters.length > 0 ? { filters } : {}), ...(groupBy ? { groupBy, } : {}), }, }; return this.fetch<notion.CollectionInstance>({ endpoint: "queryCollection", body, gotOptions, }); } private async fetch<R>({ endpoint, body, gotOptions, }: { endpoint: string; body: unknown; gotOptions?: OptionsOfJSONResponseBody; }) { const url = `${this._apiBaseUrl}/${endpoint}`; const json = true; const method = "POST"; const headers: Record<string, string> = { "Content-Type": "application/json", }; if (this._authToken) { headers.cookie = `token_v2=${this._authToken}`; } if (this._activeUser) { headers["x-notion-active-user-header"] = this._activeUser; } try { const res = await got.post(url, { ...gotOptions, json, method, body, headers, }); return res.body as R; } catch (err) { console.error(`NotionAPI error: ${err.message}`); throw err; } } private async getSignedFileUrls( urls: SignedUrlRequest[], gotOptions?: OptionsOfJSONResponseBody ): Promise<SignedUrlResponse> { return this.fetch<SignedUrlResponse>({ endpoint: "getSignedFileUrls", body: { urls }, gotOptions, }); } private async getBlocks( blockIds: string[], gotOptions?: OptionsOfJSONResponseBody ): Promise<notion.PageChunk> { return this.fetch<notion.PageChunk>({ endpoint: "syncRecordValues", body: { requests: blockIds.map((blockId) => ({ id: blockId, table: "block", version: -1, })), }, gotOptions, }); } }
要在 Next.js 頁面中取得 Notion 資料庫內容,您可以使用封裝的 NotionAPI 類別並將檢索到的資料傳遞給react-notion-x 元件進行渲染。
// pages/[pageId].tsx import { GetServerSideProps } from 'next'; import { NotionAPI } from '../lib/NotionAPI'; import { NotionRenderer } from 'react-notion-x'; import 'react-notion-x/src/styles.css'; export const getServerSideProps: GetServerSideProps = async (context) => { const { pageId } = context.params; const notion = new NotionAPI(); const recordMap = await notion.getPage(pageId as string); return { props: { recordMap, }, }; }; const NotionPage = ({ recordMap }) => { return <NotionRenderer recordMap={recordMap} fullPage={true} darkMode={false} />; }; export default NotionPage;
為了確保[pageId].tsx檔案能夠動態渲染不同的Notion頁面,需要在Next.js中設定動態路由。這將允許您匹配 pageId 參數並獲取相應的 Notion 頁面內容。
以下是設定動態路由的方法:
在您的頁面目錄中,建立一個名為 [pageId].tsx 的新檔案:
pages/[pageId].tsx
在 [pageId].tsx 檔案中,根據 URL 中的 pageId 取得 Notion 內容並動態渲染。
範例程式碼:
import { GetStaticProps, GetStaticPaths } from 'next'; import { NotionRenderer } from 'react-notion-x'; import { getNotionPageData } from '../lib/NotionAPI'; import 'react-notion-x/src/styles.css'; export default function NotionPage({ pageData }) { return ( <div> <NotionRenderer recordMap={pageData} fullPage={true} darkMode={false} /> </div> ); } export const getStaticProps: GetStaticProps = async ({ params }) => { const { pageId } = params!; const pageData = await getNotionPageData(pageId as string); return { props: { pageData, }, revalidate: 10, // Revalidate content every 10 seconds (ISR) }; }; export const getStaticPaths: GetStaticPaths = async () => { return { paths: [], // We’ll use fallback to handle dynamic routes fallback: 'blocking', // Generate pages on the fly if not pre-rendered }; };
By following the steps above, you can now encapsulate Notion API requests and render Notion pages dynamically in your Next.js project using react-notion-x. This setup allows you to efficiently integrate Notion as a CMS while ensuring scalability and maintainability in your Next.js application.
以上是【個人網站】Next如何整合Notion資料庫的詳細內容。更多資訊請關注PHP中文網其他相關文章!