To integrate a Notion database into a Next.js project, you can use Notion as a content management system (CMS) and display its content on your website. Below is a simple step-by-step guide to help you integrate the Notion database into Next.js.
Basic Preparation
Obtain Notion API Key and Database ID
- Get Notion API Key: Go to the Notion Developer Portal and create a new integration. Once created, you will receive an API key.
- Get Database ID: Navigate to the Notion database you want to integrate, and copy the URL of the database page. The database ID is the string of characters between https://www.notion.so/ and ?v= in the URL.
还不清楚的可以看上篇文章:【01.【个人网站】如何使用Notion作为数据库进行全栈开发】
Using the Official SDK
Step 1: Install Dependencies
First, you need to install Notion’s official SDK, @notionhq/client
, to communicate with the Notion API. You can install it using npm or yarn:
npm install @notionhq/client
# or
yarn add @notionhq/client
Step 2: Set up the Notion Client
Create a file lib/notion.js
in the root directory of your Next.js project and configure the Notion client as follows:
// 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;
};
Make sure to store your Notion API key in an environment variable NOTION_API_KEY.
(.env.local
)
NOTION_API_KEY=your_secret_api_key
Step 3: Fetch Database Content
In your Next.js page, you can use getStaticProps
or getServerSideProps
to fetch the content from the Notion database.
// 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>
);
}
Ensure that you have your Notion database ID stored in NOTION_DATABASE_ID
as an environment variable.
NOTION_DATABASE_ID=your_database_id
Step 4: Deployment and Verification
Finally, deploy your Next.js project to Vercel or another platform, and verify that you can successfully fetch and display data from the Notion database.
Additional Tips
- You can display different properties of Notion pages (like Name, Tags, Date, etc.) on your page.
- Consider using getServerSideProps to fetch data on every request or use getStaticProps with ISR (Incremental Static Regeneration) to optimize performance.
By following these steps, you can successfully integrate a Notion database into your Next.js project and use it to manage and display content.
Using Notion API with Wrapped URLs
Lastly, I ended up using this method for convenience when integrating with the react-notion-x
components.
Make sure to add your Notion Database ID and Notion API Key to the .env.local file:
NOTION_DATABASE_ID=your_database_id
NOTION_API_KEY=your_secret_api_key
Step 1: Install the necessary dependencies
First, install the necessary dependencies such as notion-types,
notion-utils
, and got
to handle requests to the Notion API.
npm install notion-types notion-utils got p-map
Step 2: Create a file to encapsulate Notion API interactions
In your Next.js project, create a file, for example, lib/NotionAPI.ts
, which will encapsulate interactions with the Notion API. This file will contain methods for calling Notion API endpoints to fetch data from pages and collections.
// 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,
});
}
}
Step 3: Use the Encapsulated API in Next.js Pages
To fetch Notion database content in your Next.js pages, you can use the encapsulated NotionAPI class and pass the retrieved data to react-notion-x components for rendering.
// 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;
Step 4: Configure Routing in Next.js
To ensure that the [pageId].tsx file can dynamically render different Notion pages, you need to set up dynamic routing in Next.js. This will allow you to match the pageId parameter and fetch the corresponding Notion page content.
Here’s how you can configure dynamic routes:
- Create a Dynamic Route File in the pages directory:
In your pages directory, create a new file called [pageId].tsx:
pages/[pageId].tsx
- Implement Dynamic Page Rendering:
In the [pageId].tsx file, fetch the Notion content based on the pageId from the URL and render it dynamically.
Example code:
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
};
};
- Explanation:
- getStaticPaths: Since your content is dynamic, we return an empty array for paths and set fallback: 'blocking' to generate pages on demand.
- getStaticProps: Fetches the Notion page content based on the pageId passed in the URL.
Summary
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.
Top comments (0)