Based on Next.js and Notion Public API, this blog system supports embedding MDX code in Notion to achieve more enriched effects.
- Built using Next.js, TS, Tailwind CSS, and other plugins (Shiki, React-pdf, and more).
- Uses Notion as the CMS, supporting embedding MDX code in Notion.
- Utilizes the Notion Public API, supporting cached data to improve overall performance.
- Supports dark mode and multi-language support.
- Supports Next SSG.
- SEO-friendly.
- Uses umami as the website analytics tool.
- Use supabase to manage user siginup/signin
- Shiki: Renders
code
blocks. - React-pdf: Renders
pdf
blocks. - iframely / unfurl: Renders
bookmark
,link-preview
, andvideo
blocks (Notion only returns URLs without Open Graph infos). - Katex: Renders
equation
blocks. - MDX: Renders mdx
- Duplicate this Notion template and edit your blog.
- Follow Notion's getting started guide to get a
NOTION_TOKEN
and aNOTION_DATABASE_ID
.
- Set up: Star and Fork the repository.
- Install the dependencies:
npm install
- Set up your
.env
file withNOTION_TOKEN
andNOTION_DATABASE_ID
:
NOTION_TOKEN=
NOTION_DATABASE_ID=
# for supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
- Run locally:
# Locally
npm run dev
# Production
npm run build
npm run start
Open http://localhost:3000 in your browser to see the result.
-
Why choose Notion as the content editor? I have been using Notion for years, and previously, my blog workflow involved copying content from Notion to a Markdown editor like Hexo before publishing. This process was cumbersome and inefficient, as it required converting to Markdown format. By using Notion as the CMS, I can publish content directly, eliminating the need for frequent synchronization and edits.
-
Why rebuild the wheel instead of using existing capabilities like react-notion-x? The main reason is that most implementations on Git are based on Notion's non-public API, which has the following drawbacks:
- The non-public API requires publishing Notion data to the external web, compromising data privacy.
- The non-public API relies on cookies, which may be deprecated or changed in the future, affecting stability.
- When testing, the non-public API has issues with pulling content when the page size is too large. By contrast, the public API supports pagination, providing more reliable stability.
Although the public API lacks advanced features like Database View, its basic capabilities are sufficient for most use cases. Therefore, I chose to adopt the Notion Public API for this implementation.
Most common block types are supported. But some blocks information not supported in Public API, So we give an abbreviated rendering.
Block Type | Supported | Notes |
---|---|---|
Paragraph | ✅ Yes | |
Headings | ✅ Yes | |
Bookmark | ✅ Yes | Use unfurl.js |
Bulleted List | ✅ Yes | |
Callout | ✅ Yes | |
Child Databases | ✅ Yes | Use tanstack/table |
Child page | ✅ Yes | |
Code | ✅ Yes | Use shiki |
Column list and column | ✅ Yes | |
Embed | ✅ Yes | |
Equation | ✅ Yes | Use katex |
File | ✅ Yes | |
Image | ✅ Yes | No position、size info in API |
Link Preview | ✅ Yes | Use unfurl.js |
Mention | ✅ Yes | Only Date |
Numbered List | ✅ Yes | |
✅ Yes | ||
Quote | ✅ Yes | |
Synced block | ✅ Yes | |
Table | ✅ Yes | |
To do | ✅ Yes | |
Toggle blocks | ✅ Yes | |
Video | ✅ Yes | Use iframely |
Breadcrumb | ❌ Missing | Not planned |
Template | ❌ Missing | Not planned. |
Divider | ❌ Missing | API Unsupported. |
Table Of Contents | ❌ Missing | API Unsupported. |
-
For block types such as
image
,video
,bookmark
, andlink-preview
, the Notion API only returns URLs without rendering the relevant data structures (e.g., title, description, icon, etc.). Therefore, an alternative solution is needed, and iframely is chosen for this purpose. Notion officially also uses iframely. -
Since iframely requires payment, unfurl.js is used as a fallback option, although there may be some differences in effectiveness.
- Top Navigation
- Supported environment variables including
NOTION_TOKEN
,NOTION_DATABASE_ID
, etc.
- Set in the .env file for self-deployment or on the Vercel dashboard.
- Enable MDX:
RENDER_MDX=true
and set the language to markdown in Notion's code block. - Enable Redis caching:
REDIS_URL="redis://localhost:6379"
, otherwise, memory and file caching will be used. - Notion data expiration time:
NEXT_DATACACHE_EXPIRE
, defaulting to 1 hour. Note that if Notion is used to store files such as PDFs or images, the expiration time should not be too long to avoid expired file links.