Integrating Notion API with Website
Notion as a headless CMS is a niche but practical solution for teams already maintaining documentation in Notion. A Notion database becomes the backend for a blog, documentation, or catalog. Content is edited in Notion, the website retrieves data via the official API.
Notion API: Working with Databases
import { Client, isFullPage } from '@notionhq/client';
const notion = new Client({ auth: process.env.NOTION_API_KEY });
// Retrieve records from database
async function getBlogPosts(): Promise<BlogPost[]> {
const response = await notion.databases.query({
database_id: process.env.NOTION_BLOG_DB!,
filter: {
property: 'Published',
checkbox: { equals: true }
},
sorts: [{ property: 'Date', direction: 'descending' }],
});
return response.results
.filter(isFullPage)
.map(page => ({
id: page.id,
title: (page.properties.Title as any).title[0]?.plain_text ?? '',
slug: (page.properties.Slug as any).rich_text[0]?.plain_text ?? '',
date: (page.properties.Date as any).date?.start ?? '',
excerpt: (page.properties.Excerpt as any).rich_text[0]?.plain_text ?? '',
cover: page.cover?.type === 'external' ? page.cover.external.url : null,
tags: (page.properties.Tags as any).multi_select.map((t: any) => t.name),
}));
}
Retrieving Page Content
Notion stores content as blocks. notion-to-md converts them to Markdown:
import { NotionToMarkdown } from 'notion-to-md';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkHtml from 'remark-html';
const n2m = new NotionToMarkdown({ notionClient: notion });
async function getPageContent(pageId: string): Promise<string> {
const mdBlocks = await n2m.pageToMarkdown(pageId);
const mdString = n2m.toMarkdownString(mdBlocks);
const result = await unified()
.use(remarkParse)
.use(remarkHtml)
.process(mdString.parent);
return String(result);
}
ISR with On-Demand Revalidation
When a record in Notion changes, Zapier or Make.com call a website webhook that invalidates the cache for that specific page:
// Next.js: on-demand revalidation
export async function POST(request: Request) {
const { page_id, slug } = await request.json();
await revalidatePath(`/blog/${slug}`);
await revalidatePath('/blog');
return Response.json({ revalidated: true });
}
Limitations
- Rate limit: 3 requests per second — caching is mandatory
- Notion does not support native webhooks (only via Zapier/Make)
- Some blocks (databases within pages, synced blocks) are not returned by the API
Timeline
Website with Notion as CMS (list + detail pages): 3–5 business days.







