A modern blog built with Next.js and Notion as a CMS.
- π Next.js 15 with App Router
- π Notion as a CMS
- Seamless integration with Notion databases
- Real-time content updates
- No need to redeploy when content changes
- Support for rich text, images, and other Notion blocks
- π¨ Tailwind CSS for styling
- π Dark mode support
- π± Fully responsive design
- π SEO optimized
- π Markdown rendering with code highlighting
- π Automatic conversion from Notion blocks to Markdown
- π·οΈ Tag-based categorization
- π Date-based sorting and filtering
- Node.js 18.17 or later
- A Notion account
- A Notion integration (create one at https://www.notion.so/my-integrations)
- Clone the repository:
git clone https://github.com/yourusername/next-notion-blog.git
cd next-notion-blog
- Install dependencies:
npm install
- Set up your Notion database:
npm run setup-notion
This script will guide you through creating a Notion database with the correct structure for your blog.
Alternatively, you can manually set up your Notion database by following the instructions in NOTION-CMS-SETUP.md.
- Start the development server:
npm run dev
- Open http://localhost:3000 in your browser.
Create a .env.local
file in the root directory with the following variables:
NOTION_API_KEY=your_notion_api_key_here
NOTION_DATABASE_ID=your_notion_database_id_here
- Go to https://www.notion.so/my-integrations
- Click "New integration"
- Name your integration (e.g., "Next.js Blog")
- Select the workspace where you'll create your blog database
- Set the capabilities (at minimum, you need "Read content" permission)
- Click "Submit" to create your integration
- Copy the "Internal Integration Token" - this will be your
NOTION_API_KEY
You have two options for setting up your Notion database:
Run the following command:
npm run create-database
This script (scripts/create-database-in-page.js
) will:
- Connect to Notion using your API key
- Create a new database inside an existing Notion page
- Set up the required properties (Title, Slug, PublishedDate, Summary, Tags)
- Update your
.env.local
file with the new database ID
- Create a new page in Notion
- Add a database to this page with the following properties:
- Title (title): The title of your blog post
- Slug (text): URL-friendly version of the title
- PublishedDate (date): Publication date
- Summary (text): Brief description of the post
- Tags (multi-select): Categories for your post
- Status (status): Publication status (optional)
- Share the database with your integration:
- Click "Share" in the top right of your database
- Click "Add people, groups, or integrations"
- Search for your integration name and select it
- Click "Invite"
- Copy the database ID from the URL:
- The database ID is the part after the workspace name and before the question mark
- Example:
https://www.notion.so/workspace/1a2b3c4d5e6f7g8h9i0j?v=...
- In this example,
1a2b3c4d5e6f7g8h9i0j
is your database ID
-
lib/notion-api.ts
: Core file that handles all Notion API interactionsgetDatabaseId
: Validates and formats the database IDgetAllPosts
: Fetches all blog posts from the Notion databasegetPostBySlug
: Fetches a specific blog post by its slugpageToPostMetadata
: Converts Notion page properties to blog post metadata
-
scripts/create-database-in-page.js
: Creates a properly structured database in an existing Notion page -
.env.local
: Stores your Notion API key and database ID
-
Initialization: The app initializes a Notion client using your API key:
// From lib/notion-api.ts const notion = new Client({ auth: process.env.NOTION_API_KEY })
-
Fetching Posts: When the blog page loads, it calls
getAllPosts()
which:- Connects to your Notion database using the database ID
- Queries all entries, sorted by publication date
- Converts each Notion page to a blog post metadata object
-
Fetching a Single Post: When viewing a specific blog post,
getPostBySlug()
:- Queries the database for a page with the matching slug
- Retrieves the page content as blocks
- Converts the blocks to Markdown for rendering
-
Real-time Updates: Any changes you make in Notion are reflected on your site:
- Add a new post in Notion β It appears on your blog
- Edit a post in Notion β Changes appear on your blog
- No need to redeploy your site
This blog uses a sophisticated system to convert Notion content to Markdown and render it beautifully:
The notion-to-md
package converts Notion blocks to Markdown:
// From lib/notion-api.ts
import { NotionToMarkdown } from 'notion-to-md'
const n2m = new NotionToMarkdown({ notionClient: notion })
// In getPostBySlug function:
const mdBlocks = await n2m.pageToMarkdown(page.id)
const content = n2m.toMarkdownString(mdBlocks)
The converted Markdown is rendered using react-markdown
with several plugins:
// From components/markdown-renderer.tsx
import ReactMarkdown from 'react-markdown'
import rehypeRaw from 'rehype-raw'
import rehypeSanitize from 'rehype-sanitize'
import remarkGfm from 'remark-gfm'
// These packages enable:
// - remarkGfm: GitHub Flavored Markdown (tables, strikethrough, etc.)
// - rehypeRaw: Render HTML within Markdown
// - rehypeSanitize: Prevent XSS attacks by sanitizing HTML
Code blocks from Notion are automatically highlighted using syntax highlighting:
// From components/markdown-renderer.tsx
// Code block rendering with syntax highlighting
const components = {
code({ node, inline, className, children, ...props }) {
// Implementation of code highlighting
},
}
- Open your Notion database
- Create a new page
- Fill in the following properties:
- Title: The title of your blog post
- Slug: The URL-friendly version of the title (e.g., "my-first-blog-post")
- PublishedDate: The date you want to publish the post
- Summary: A brief description of the post
- Tags: Categories for your post
- Status: Set to "Published" when ready to show on your site (if you added this property)
- Add your content to the page body using Notion's editor
- All content formatting in Notion will be preserved
- Images, lists, quotes, code blocks, etc. are all supported
- Changes are reflected on your site in real-time
You can customize the blog components in:
components/blog-post-card.tsx
- For the blog post preview cardscomponents/markdown-renderer.tsx
- For styling the blog post contentapp/(root)/blog/page.tsx
- For the blog listing pageapp/(root)/blog/[slug]/page.tsx
- For the individual blog post page
-
"Could not find database with ID": Make sure:
- Your database ID is correct
- You've shared the database with your integration
- Your integration has the necessary permissions
-
"Could not find property with name or id": Make sure your database has all the required properties (Title, Slug, PublishedDate, Summary, Tags)
-
No posts showing up: Check that:
- You have added posts to your database
- The posts have all required fields filled in
- If you're filtering by Status, make sure the posts have the correct status
The application logs helpful information to the console:
- Database ID being used
- API request URLs
- Error messages from the Notion API
This project is licensed under the MIT License - see the LICENSE file for details.