Post

Use notion as CMS for Jekyll

Hi

Several days ago, I got familiar with Next.js and i wasn abale to create a blog using Vercel (mymoment.vercel.app) and I was curious how can I deploy it to cloudflare

After reading and raising questions, I realized that Cloudflare Pages is only a STATIC page host and I can’t use it like Vercel to deploy live Next.js site (after editing Notion, I have to redeploy the site)

On the other hand, I had a website based on Jekyll and I am more comfortable with Jekyll (its alos more developed, maybe because its used by Github Pages)

Afterward, I decided to look for a way to use Notion as CMS for Jekyll

Time wasted ☹️


Tea break

With my friends


Okay, I did it, but how…

a little different 😉

I found this blog post which was helpful

First I created a package.json file in root directory. content:

1
2
3
4
5
6
7
8
9
10
11
{
	"scripts": {
		"start": "node notion.js"
	},
	"dependencies": {
		"@notionhq/client": "^1.0.4",
		"dotenv": "^16.0.1",
		"notion-to-md": "^2.5.4"
	},
	"type": "module"
}

⚠️ remember to follow updates and change version number

then I created notion.js in root directory as well. content: (updated on Aug 3th)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { Client } from '@notionhq/client'
import dotenv from 'dotenv'

import { NotionToMarkdown } from 'notion-to-md'
import * as fs from 'fs'

dotenv.config()
const notion = new Client({
  auth: process.env.NOTION_TOKEN,
})

const response = await notion.databases.query({
  database_id: process.env.NOTION_DATABASE_ID,
})

const n2m = new NotionToMarkdown({ notionClient: notion})

const kebabCase = str => str
        .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
        .join('-')
        .toLowerCase();
const pages = response
  .results
  .filter(x => x.properties.Name.title.length > 0)

pages.map(async x => {
  const mdBlocks = await n2m.pageToMarkdown(x.id)
  const content = n2m.toMarkdownString(mdBlocks)
  const createdDate = x.created_time.split('T')[0]
  //const createdTime = x.created_time
  const publishTime = x.properties.Published.date.start
  const topCategories = x.properties.TopCategory.select.name
  const subCategories = x.properties.SubCategory.select.name
  const mathCheck = x.properties.Math.checkbox
  const mermaidCheck = x.properties.Mermaid.checkbox
  const title = x.properties.Name.title[0].plain_text
  const filename = kebabCase(title)
  const tags = x.properties.Tags.multi_select
    .map(x => `  - ${x.name}`)
    .join('\n')
  const pageContent =
`---
title: ${title}
date: ${publishTime}
tags:
${tags}
categories: [${topCategories}, ${subCategories}]
math: ${mathCheck}
mermaid: ${mermaidCheck}
---
${content}
`
  fs.writeFile(`./_posts/${createdDate}-${filename}.md`, pageContent, (err) => {
    console.log(err);
  });
  return
});

Updates:

  • use publishTime instead on created_time so the time can be modified by a property
  • use topCategories and subCategories to categorize posts (note that my theme support only one-step subcategory)
  • use mathCheck and mermaidCheck to enable Math and Mermaid support in posts

And a notion.yml in .github/workflows/notion.yml content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
name: Build Notion Posts

on:
  schedule:
  - cron: "*/30 * * * *"
# change branch based on your repository  
  push:
    branches: [ "note" ]
  pull_request:
    branches: [ "note" ]
    
  workflow_dispatch:


jobs:
  build_posts:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 15.x
      - name: Run a multi-line script
        run: |
          touch .env
          echo 'NOTION_TOKEN=$' >> .env
          echo 'NOTION_DATABASE_ID=$' >> .env
          yarn
          yarn start
      - uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: build pages from notion

👉 cron set a period based on your preference

⚠️ create NOTION_TOKEN and NOTION_DATABASE_ID in [ settings → Secrets → Actions ] and set their proper value ( I will explain ) Github Action secrets

and booom, its done


Token and ID

  1. Create a Notion Integration → https://developers.notion.com/docs/getting-started Copy Internal Integration Token, Its your NOTION_TOKEN
  2. Duplicate a template page https://notion.so/7875426197cf461698809def95960ebf
  3. Extract database ID

    Its your NOTION_DATABASE_ID

1
2
https://www.notion.so/myworkspace/a8aec43384f447ed84390e8e42c2e089?v=...
                                  |--------- Database ID --------|

Enjoy


Oh oh oh wait

I found jekyll-notion which is more helpful and can handle the convertion faster with less steps :)

Personally, I think the npm script is better 🤨

But

jekyll-notion is more compatible with Cloudflare Pages 🤔 Let’s say, you dont need to import Form Notion to Github and then push it to cloudflare

By jekyll-notion Ruby add-ons, Notion database is imported during deployment (on Cloudflare)


Last stage

overall, I decided to use notion-to-md (npm) package to import my notion to github.

Tweaks

  • I use Github API to run Integration workflow and update .md files based on changed Notion block content.

Personal Advice:

  • create another branch on you github repo and import .md files from Notion to that branch (using automated script and github action mentioned above)
  • After importing, Cloudflare starts redeploying.
  • CF-Pages creates a subdomain with the name of that branch (e.g. import.alireza.pages.dev) and you can review deployment before pushing to main website 😉
  • CF-Pages requires 2-5 min to deploy your website.
  • I use Cloudflare Pages Hooks to update my website

Hope you enjoy it

This post is licensed under CC BY 4.0 by the author.