alex-guoba/next-blogger

March 20, 2024

Next-Blogger built on Next.js 14+ and Tailwind CSS, use Notion to manage your content.

Features

  1. Built using Next.js(14+ with App Router ), Typescript, Tailwind CSS, and other plugins (Shiki, React-pdf, and more).
  2. Utilizes the Notion Public API.
  3. Supports caching Notion data using Prisma to reduce API calls and improve overall performance.
  4. Includes a dark mode option.
  5. SEO friendly with RSS feed, sitemaps and more!
  6. Includes load testing scripts, see load-testing.
  7. Supports Server Rendering and Dynamic Rendering.

Tech Stack

Frameworks

  1. Next.js
  2. Notion
  3. Tailwind CSS and shadcn
  4. Prisma

Components

  1. Shiki: Renders code blocks.
  2. React-pdf: Renders pdf blocks.
  3. iframely / unfurl: Renders bookmarklink-preview, and video blocks (Notion only returns URLs without Open Graph infos).
  4. Katex: Renders equation blocks.

Platforms

(To be added)

✨ Getting Started

Prerequisites

  1. Duplicate this Notion template and edit your blog.
  2. Follow Notion's getting started guide to get a NOTION_TOKEN and a NOTION_DATABASE_ID.

Development

  1. Set up: Star and Fork the repository.
  2. Install the dependencies:
[text]
npm install
  1. Set up your .env file with NOTION_TOKEN and NOTION_DATABASE_ID:
[text]
NOTION_TOKEN=
NOTION_DATABASE_ID=
  1. (Optional) For performance considerations, it is recommended to configure database caching. See the Prisma documentation for various database Connection URLs. The default in the code is postgresql:
[text]
datasource db {
  provider = "postgresql"  url      = env("DATABASE_URL")}
[text]
DATABASE_URL="postgres://xxxx"

The first time you run it, you need to create the relevant table structure.

[text]
npm run db:init
  1. Run locally:
[text]
# Locally
npm run dev

# Production
npm run build
npm run start

Open http://localhost:3000 in your browser to see the result.

deploy

Follow the deployment guides for VercelNetlify and Docker for more information.

Supported Blocks

Most common block types are supported. But some blocks information not supported in Public API, So we give an abbreviated rendering.

Block TypeSupportedNotes
Paragraph✅ Yes 
Headings✅ Yes 
Bookmark✅ YesUse unfurl.js
Bulleted List✅ Yes 
Callout✅ Yes 
Child Databases✅ YesUse tanstack/table
Child page✅ Yes 
Code✅ YesUse shiki
Column list and column✅ Yes 
Embed✅ Yes 
Equation✅ YesUse katex
File✅ Yes 
Image✅ YesNo position、size info in API
Link Preview✅ YesUse unfurl.js
Mention✅ YesOnly Date
Numbered List✅ Yes 
PDF✅ Yes 
Quote✅ Yes 
Synced block✅ Yes 
Table✅ Yes 
To do✅ Yes 
Toggle blocks✅ Yes 
Video✅ YesUse iframely
Breadcrumb❌ MissingNot planned
Template❌ MissingNot planned.
Divider❌ MissingAPI Unsupported.
Table Of Contents❌ MissingAPI Unsupported.

Performance

  1. Dynamic Routes

Disabling dynamic routes can reduce redundant rendering and significantly improve performance. Below is a performance comparison between enabling and disabling this feature:

  • force-dynamic:
Bucket#%!(MISSING) Histogram
[0s, 10ms]00.00%!(MISSING)
[10ms, 40ms]10.01%!(MISSING)
[40ms, 80ms]00.00%!(MISSING)
[80ms, 200ms]10.01%!(MISSING)
[200ms, 500ms]90610.30%!(MISSING) #######
[500ms, 1s]788589.67%!(MISSING) ###################################################################
[1s, +Inf]00.00%!(MISSING)
  • auto (default):
Bucket#%!(MISSING) Histogram
[0s, 10ms]00.00%!(MISSING)
[10ms, 40ms]772780.49%!(MISSING) ############################################################
[40ms, 80ms]181618.92%!(MISSING) ##############
[80ms, 200ms]540.56%!(MISSING)
[200ms, 500ms]30.03%!(MISSING)
[500ms, 1s]00.00%!(MISSING)
[1s, +Inf]00.00%!(MISSING)

Learn More

Why Notion?

  1. Why choose Notion as the content editor? I have been using Notion for many years. In the past, when creating blogs, I would write content in Notion, copy it to editors like Markdown, and then publish it using hexo. This process was cumbersome and required converting the format to Markdown, which was inconvenient. By using Notion as a CMS, I can directly publish content written in Notion without the need for frequent synchronization after making changes.
  2. Why reinvent the wheel instead of using existing solutions like react-notion-x? The main reason is that most implementations on GitHub are based on Notion's unofficial API, which has several drawbacks:
  • The unofficial API does not have official documentation, so one has to figure out the data structure on their own.
  • The unofficial API requires publishing Notion data to the internet, which may compromise data privacy.
  • The unofficial API may be deprecated or changed in the future, making it unstable.
  • During testing, I found that the unofficial API sometimes fails to fetch large pages. In contrast, the public API supports pagination and is more stable. In summary, while the Public API lacks some advanced features like Database View support, it meets the basic requirements. Therefore, I chose to implement it using the Notion Public API.
  1. Why Use Prisma to Cache Notion Data

The Notion API has frequency limitations, and for some deeply nested pages, multiple accesses can easily exceed these limitations. Additionally, API access can be unstable in certain regions, significantly impacting page performance. To address these issues, caching is used. Furthermore, some data returned by the Notion API, such as image URLs, have a limited validity period, necessitating cache invalidation. By default, the cache expires after one hour. For more details on expiration strategies, refer to the official documentation on notion-hosted-files.

Prisma is chosen as the caching component due to its flexibility and support for various databases, including MySQL, MongoDB, and others. For more information, refer to the official Doc.

Why iframely?

  1. For block types such as imagevideobookmark, and link-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.
  2. Since iframely requires payment, unfurl.js is used as a fallback option, although there may be some differences in effectiveness.

TODO

Reference

  1. Notion Public API
See all postsSee all posts