要将 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中文网其他相关文章!