Automated content management system that syncs blog posts from Notion to GitHub Pages as markdown files.
This repository serves as a content backend for a static blog. It fetches pages from a Notion database, converts them to markdown with frontmatter metadata, and publishes them to GitHub Pages for consumption by a frontend application.
- ✅ Incremental Sync – Only fetches and converts modified Notion pages
- ✅ Frontmatter Metadata – Extracts title, dates, tags, and custom properties
- ✅ Image Handling – Keeps images as Notion URLs (no local storage)
- ✅ Automated Workflow – Daily sync via GitHub Actions
- ✅ Manual Trigger – Run sync on-demand via GitHub Actions UI
- ✅ Deleted Page Cleanup – Automatically removes archived Notion pages
- Create a Notion integration at https://www.notion.so/my-integrations
- Copy the Internal Integration Token
- Share your Notion database with the integration
- Copy your Database ID from the database URL
# Clone the repository
git clone https://github.com/thorbeck/abytegray-content.git
cd abytegray-content
# Install dependencies
npm install
# Create .env file
cp .env.example .env
# Add your Notion credentials to .env
NOTION_API_KEY=your_integration_token_here
NOTION_DATABASE_ID=your_database_id_here
# Run sync locally
npm run syncFor automated syncing via GitHub Actions:
- Go to repository Settings → Secrets and variables → Actions
- Add two repository secrets:
NOTION_API_KEY: Your Notion integration tokenNOTION_DATABASE_ID: Your Notion database ID
- Go to repository Settings → Pages
- Set Source to "Deploy from a branch"
- Select Branch:
mainand Folder:/ (root) - Save
Your content will be available at: https://thorbeck.github.io/abytegray-content/
Run locally:
npm run syncOr trigger via GitHub Actions:
- Go to Actions tab
- Select Sync Notion Content workflow
- Click Run workflow
The workflow runs automatically:
- Daily at 2 AM UTC
- On push to main (when sync.js or workflow changes)
The sync script automatically extracts common Notion properties:
| Property Type | Frontmatter Field |
|---|---|
| Title | title |
| Select | Property name (single value) |
| Multi-select | Property name (array) |
| Date | Property name (ISO string) |
| Checkbox | Property name (boolean) |
| Number | Property name (number) |
| Rich Text | Property name (plain text) |
Example Notion properties:
Status(select) →Status: "Published"Tags(multi-select) →Tags: ["JavaScript", "Tutorial"]Published(date) →Published: "2024-01-30"
---
title: "My Blog Post"
slug: "my-blog-post"
notionId: "abc123..."
createdTime: "2024-01-15T10:00:00.000Z"
lastEditedTime: "2024-01-30T15:20:00.000Z"
Status: "Published"
Tags:
- JavaScript
- Tutorial
Published: "2024-01-30"
---
# Content starts here...abytegray-content/
├── .github/
│ └── workflows/
│ └── sync-notion.yml # GitHub Actions workflow
├── content/ # Generated markdown files
│ └── README.md
├── sync.js # Main sync script
├── .notion-sync-state.json # Sync state (auto-generated)
├── .nojekyll # Disable Jekyll processing
├── .gitignore
├── package.json
└── README.md
- Load Sync State – Read
.notion-sync-state.jsonfor last sync timestamp - Query Notion – Fetch pages with
last_edited_time > lastSyncTime - Convert to Markdown – Use
notion-to-mdlibrary - Generate Frontmatter – Extract metadata from page properties
- Write Files – Save to
content/directory with slug-based filenames - Handle Deletions – Remove markdown files for archived Notion pages
- Update State – Save new sync timestamp and page tracking info
- Commit & Push – GitHub Actions commits changes back to repository
Your frontend can fetch content from GitHub Pages:
// Fetch all markdown files
const response = await fetch('https://thorbeck.github.io/abytegray-content/content/my-blog-post.md');
const markdown = await response.text();
// Parse frontmatter and content
// Use a library like gray-matter to extract YAML frontmatterOr create an index JSON file for easier discovery (optional enhancement).
- Check that
NOTION_API_KEYis set correctly in GitHub Secrets - Verify the integration has access to your database
- Check that pages are not archived in Notion
- Verify pages are in the correct database
- Check GitHub Actions logs for errors
- Ensure property names match (case-sensitive)
- Check that properties have values in Notion
- Review
sync.jslogs for warnings
ISC
This is a personal project, but suggestions and bug reports are welcome via GitHub Issues.