---
title: "Building a Blog with Notion as Your CMS"
description: "Learn how to turn Notion into a powerful content management system for your Astro blog — write in Notion, publish to the web automatically."
pubDate: "2026-03-10"
category: tech
canonicalUrl: "https://demo.hexink.com/default/tech/building-a-blog-with-notion-cms/"
language: zh-TW
structuredDataType: TechArticle
tags: ["notion","cms","astro","tutorial"]
heroImage: "https://images.unsplash.com/photo-1517694712202-14dd9538aa97?w=1200&h=630&fit=crop"
heroAlt: "Laptop on a wooden desk with code editor open"
series: "Notion-Powered Blog"
seriesOrder: 1
---

If you've ever wished you could just write in Notion and have your blog update automatically, you're not alone. Notion's rich editor, database features, and collaborative tools make it an ideal CMS — if you can bridge the gap to your static site.

In this guide, we'll walk through setting up Notion as a headless CMS for an Astro blog.

## Why Notion as a CMS?

Most headless CMS platforms (Contentful, Sanity, Strapi) are powerful but come with a learning curve and often a price tag. Notion offers something different:

- **Familiar interface** — If you already use Notion for notes, you know the editor
- **Rich content** — Tables, callouts, toggles, embedded media
- **Database views** — Filter by status, category, or publish date
- **Free tier** — More than enough for a personal blog
- **Collaboration** — Invite editors without teaching them a new tool

## Architecture Overview

Here's how the pieces fit together:

```
┌─────────┐     Webhook     ┌──────────────┐     Dispatch     ┌─────────┐
│  Notion  │ ──────────────► │  CF Worker   │ ───────────────► │ GitHub  │
│   DB     │                 │  (validate)  │                  │ Actions │
└─────────┘                 └──────────────┘                  └────┬────┘
                                                                   │
                                                              sync + build
                                                                   │
                                                              ┌────▼────┐
                                                              │   CF    │
                                                              │  Pages  │
                                                              └─────────┘
```

When you change a page's status to "Publishing" in Notion, a webhook fires. A Cloudflare Worker validates the request and triggers a GitHub Actions workflow that syncs content and deploys.

## Setting Up the Notion Database

Create a new database in Notion with these properties:

| Property | Type | Purpose |
|----------|------|---------|
| Title | Title | Post title |
| Status | Select | Publishing workflow |
| Category | Select | Blog category |
| Tags | Multi-select | Post tags |
| Publish Date | Date | When to publish |
| Description | Text | SEO description |
| Hero Image | Files & Media | Cover image |
| Slug | Text | URL slug |

The **Status** property drives the entire workflow:

```
Draft → Ready → Publishing → Synced
                    ↓
              Sync Failed (on error)
```

## Connecting Notion to Your Blog

First, create a Notion integration and grab your API token:

```typescript
// sync-notion.mjs
import { Client } from '@notionhq/client';

const notion = new Client({ auth: process.env.NOTION_TOKEN });

const response = await notion.databases.query({
  database_id: process.env.NOTION_DATABASE_ID,
  filter: {
    property: 'Status',
    select: { equals: 'Publishing' },
  },
});
```

For each page, we convert Notion blocks to Markdown:

```typescript
async function pageToMarkdown(pageId: string): Promise<string> {
  const blocks = await notion.blocks.children.list({
    block_id: pageId,
  });

  return blocks.results
    .map(block => blockToMarkdown(block))
    .join('\n\n');
}
```

## The Webhook Handler

The Cloudflare Worker validates incoming webhooks using HMAC-SHA256:

```typescript
async function verifySignature(
  body: string,
  signature: string,
  secret: string,
): Promise<boolean> {
  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey(
    'raw',
    encoder.encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['verify'],
  );

  const sig = hexToBytes(signature);
  return crypto.subtle.verify('HMAC', key, sig, encoder.encode(body));
}
```

> **Security note:** Always validate webhook signatures. Without verification, anyone could trigger rebuilds on your site.

## What's Next

In the [next post](../terminal-tools-every-developer-needs/) of this series, we'll cover the developer tools that make this workflow even smoother — from CLI scaffolding to automated deployments.

---

**TL;DR:** Notion + Cloudflare Workers + GitHub Actions = a free, fast, and familiar blogging workflow. Write where you think, publish where you want.

## FAQ

### Can I use Notion as a CMS for my blog?

Yes! With the right integration, Notion becomes a full-featured CMS. You write in Notion's familiar editor, and your blog syncs automatically.

### Is the Notion API free?

The Notion API is free for integrations. You only need a free Notion account to get started.

### How fast does content sync from Notion to my blog?

With a webhook setup, content typically syncs within 30 seconds of publishing in Notion.
