# 使用 Notion 作為 Headless CMS 客製 Portfolio
2024.01 huhu
---
## AGENDA
1. 頁面 DEMO
2. 原始需求
3. 實作步驟 (Notion)
4. 延伸問題
5. 應用發想
---
<video data-autoplay src="https://i.imgur.com/oTtniUt.mp4"></video>
- 列表可篩選 Tag
- 內頁可開啟 Dark Mode
- 內頁呈現 Editor 內容 & 客製區塊
- 內頁連結自動指向下一個專案
---
## 是這樣開始的 ☄️
設計師捧朋:可以幫我的作品網站做個後台嗎
我連結常常忘記改,求求 🙏
<p><!-- .element: class="fragment" -->
能方便更新作品<br>
最好只要新增內容頁各選單連結自己就會改好<br>
(至少選單是動態的)<br>
🙏
</p>
<span><!-- .element: class="fragment" -->...</span>
----
## 📝
- 已有一個 HTML 靜態網站 = **不想重做**
- 他具備 HTML / CSS 能力 = **想要自己控制頁面**
- 我不會自己建資料庫 = **需要想解決方案**
----
## 方案選擇
- Json 🤯 ❌
- WordPress ⏳ 💸 ❌
- Notion Database 💡 👀 ✨ ✅
---
## 實作步驟
1. Notion 建立 Database
2. Notion integration 設定 (拿到 `Token`、`DatabaseID` ) 🔑
3. 串接 Notion API ⛏️👩💻
---
STEP 1
## Notion 建立 Database
----
## 新增 Database

----
## 建立 property 屬性欄位


---
STEP 2
## Notion Integration 設定
建立 Intergration (權限設定) 並綁定到 Database
🚩拿到 `Token` 、 `DatabaseID`
<br>
[官方文件](https://developers.notion.com/docs/create-a-notion-integration) 很清楚 👍
----
## 建立 Intergration
[My integrations](https://www.notion.so/my-integrations) > + New intergration

----
選擇 workspace / 填 Name

----
🚩 拿到 `Token`

----
訪問權限設定

----
## 綁定 Intergration 到 Database
右上 ... > + Add connections

----
綁完後可以看到訪問權限

----
🚩 拿到 `DatabaseID`
右上 Share > Copy link

```shell
https://www.notion.so/{workspace_name}/{database_id}?v={view_id}
^^^^^^^^^^^^^
```
---
STEP 3
## 串接 Notion API
Database - 難度 ⭐️
<small>...[官網](https://developers.notion.com/reference/intro)上有更多種類介紹</small>
----
## 🔧
[JavaScript SDK](https://github.com/makenotion/notion-sdk-js) opensource
```javascript [|1|3|6-7|9|25]
const { Client } = require('@notionhq/client');
const notion = new Client({ auth: process.env.NOTION_API_KEY });
(async () => {
const databaseId = 'd9824bdc-8445-4327-be8b-5b47500af6ce';
const response = await notion.databases.query({
database_id: databaseId,
filter: {
or: [
{
property: 'In stock',
checkbox: {
equals: true,
},
},
{
property: 'Cost of next trip',
number: {
greater_than_or_equal_to: 2,
},
},
],
},
sorts: [
{
property: 'Last ordered',
direction: 'ascending',
},
],
});
console.log(response);
})();
```
----
Query a database - Response
`properties`、`url` 👀
```jsonld [|3|24|25-33|74-78|34-47|103]
{
"object": "list",
"results": [
{
"object": "page",
"id": "35ce428e-9280-4b79-8e41-d3384c658b0f",
"created_time": "2024-01-28T12:24:00.000Z",
"last_edited_time": "2024-01-29T05:00:00.000Z",
"created_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"last_edited_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"cover": null,
"icon": null,
"parent": {
"type": "database_id",
"database_id": "5d734c14-2b0f-452a-be46-c7bc9267c85e"
},
"archived": false,
"properties": {
"date": {
"id": "CG%3CE",
"type": "date",
"date": {
"start": "2024-01-01",
"end": null,
"time_zone": null
}
},
"image": {
"id": "SXpa",
"type": "files",
"files": [
{
"name": "original-fd88f4ec44bc2fea8bef78f40c46edb9.png",
"type": "file",
"file": {
"url": "https://prod-files-secure.s3.us-west-2.amazonaws.com/b27f15fd-5247-471c-8625-516aecc78dcc/534caafd-b1ca-4c18-99a5-2c83baad3097/original-fd88f4ec44bc2fea8bef78f40c46edb9.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240130%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240130T063330Z&X-Amz-Expires=3600&X-Amz-Signature=cacdc1ff2d6558e7a586aece0edfba4f3c9c80cabdd402c6d330dcf7f21ad4a4&X-Amz-SignedHeaders=host&x-id=GetObject",
"expiry_time": "2024-01-30T07:33:30.459Z"
}
}
]
},
"tags": {
"id": "UQVh",
"type": "multi_select",
"multi_select": [
{
"id": "4a5ffa10-b1b0-4b12-ac4b-91dd293f3604",
"name": "branding",
"color": "blue"
},
{
"id": "fa8efd71-fb68-4639-85dd-7f581059d90b",
"name": "uiux design",
"color": "pink"
}
]
},
"url": {
"id": "WfDf",
"type": "rich_text",
"rich_text": []
},
"theme": {
"id": "kAGM",
"type": "select",
"select": null
},
"enabled": {
"id": "%7CFYI",
"type": "checkbox",
"checkbox": true
},
"title": {
"id": "title",
"type": "title",
"title": [
{
"type": "text",
"text": {
"content": "Abc.app",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "Abc.app",
"href": null
}
]
}
},
"url": "https://www.notion.so/Abc-app-35ce428e92804b798e41d3384c658b0f",
"public_url": null
}
],
"next_cursor": null,
"has_more": false,
"type": "page_or_database",
"page_or_database": {},
"request_id": "7d096629-b8bf-497a-aa02-8e32e81b7ce2"
}
```
---
STEP 3.2 💭
## 串接 Notion API
Page > Block Children - 難度 ⭐️⭐️⭐️⭐️
(嘗試使用 Page 當 Editor)
----

----
官方範例
```javascript= [|5-10|12-20]
const { Client } = require('@notionhq/client');
const notion = new Client({ auth: process.env.NOTION_API_KEY });
// Retrieve a page
(async () => {
const pageId = '59833787-2cf9-4fdf-8782-e53db20768a5';
const response = await notion.pages.retrieve({ page_id: pageId });
console.log(response);
})();
// Retrieve block children
(async () => {
const blockId = '59833787-2cf9-4fdf-8782-e53db20768a5';
const response = await notion.blocks.children.list({
block_id: blockId,
page_size: 50,
});
console.log(response);
})();
```
----
Retrieve a page - Response
`properties` 👀
```json= [|21]
{
"object": "page",
"id": "00cb99e8-3516-40d9-ab23-60773e070f0b",
"created_time": "2024-01-21T11:24:00.000Z",
"last_edited_time": "2024-01-30T08:07:00.000Z",
"created_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"last_edited_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"cover": null,
"icon": null,
"parent": {
"type": "database_id",
"database_id": "5d734c14-2b0f-452a-be46-c7bc9267c85e"
},
"archived": false,
"properties": {
"date": {
"id": "CG%3CE",
"type": "date",
"date": {
"start": "2023-07-01",
"end": null,
"time_zone": null
}
},
"image": {
"id": "SXpa",
"type": "files",
"files": [
{
"name": "original-6ec18eb4cb45553bfabece9055d8c917.png",
"type": "file",
"file": {
"url": "https://prod-files-secure.s3.us-west-2.amazonaws.com/b27f15fd-5247-471c-8625-516aecc78dcc/46845705-3a00-4b77-bd91-e3a61db5987b/original-6ec18eb4cb45553bfabece9055d8c917.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240130%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240130T084921Z&X-Amz-Expires=3600&X-Amz-Signature=7826e424acbb1f1eb7560b1454efa12c5827fbdd6a1f68e7ff3f21e65717b0d8&X-Amz-SignedHeaders=host&x-id=GetObject",
"expiry_time": "2024-01-30T09:49:21.107Z"
}
},
{
"name": "wp6601742-fantastic-mr-fox-wallpapers.jpg",
"type": "file",
"file": {
"url": "https://prod-files-secure.s3.us-west-2.amazonaws.com/b27f15fd-5247-471c-8625-516aecc78dcc/44efd64c-5bd4-4744-91ae-633c6cbb4326/wp6601742-fantastic-mr-fox-wallpapers.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240130%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240130T084921Z&X-Amz-Expires=3600&X-Amz-Signature=9b7d6a830abd6c7e48d2e526c97cf5978470b8a0eb2623fb71f674146e87777f&X-Amz-SignedHeaders=host&x-id=GetObject",
"expiry_time": "2024-01-30T09:49:21.123Z"
}
}
]
},
"tags": {
"id": "UQVh",
"type": "multi_select",
"multi_select": [
{
"id": "b3907d90-8bea-4add-9028-44e626d05c4e",
"name": "web design",
"color": "purple"
},
{
"id": "fa8efd71-fb68-4639-85dd-7f581059d90b",
"name": "uiux design",
"color": "pink"
}
]
},
"url": {
"id": "WfDf",
"type": "rich_text",
"rich_text": []
},
"theme": {
"id": "kAGM",
"type": "select",
"select": null
},
"enabled": {
"id": "%7CFYI",
"type": "checkbox",
"checkbox": true
},
"title": {
"id": "title",
"type": "title",
"title": [
{
"type": "text",
"text": {
"content": "B2B ",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "B2B ",
"href": null
},
{
"type": "text",
"text": {
"content": "Dashboard",
"link": null
},
"annotations": {
"bold": true,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "Dashboard",
"href": null
}
]
}
},
"url": "https://www.notion.so/B2B-Dashboard-00cb99e8351640d9ab2360773e070f0b",
"public_url": null,
"request_id": "12389a96-f279-4434-b972-a35e9a239627"
}
```
----
Retrieve block children - Response
`type`、`has_children`、`id` 👀
```json= [3|23|21|6|67|69|84|117]
{
"object": "list",
"results": [
{
"object": "block",
"id": "1ba736e5-7c07-4fe4-998e-a1b1ae8f8629",
"parent": {
"type": "page_id",
"page_id": "00cb99e8-3516-40d9-ab23-60773e070f0b"
},
"created_time": "2024-01-29T03:59:00.000Z",
"last_edited_time": "2024-01-29T03:59:00.000Z",
"created_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"last_edited_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"has_children": true,
"archived": false,
"type": "column_list",
"column_list": {}
},
{
"object": "block",
"id": "17df45d0-dce9-49aa-80b3-50614ba211b6",
"parent": {
"type": "page_id",
"page_id": "00cb99e8-3516-40d9-ab23-60773e070f0b"
},
"created_time": "2024-01-29T12:21:00.000Z",
"last_edited_time": "2024-01-29T12:21:00.000Z",
"created_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"last_edited_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"has_children": true,
"archived": false,
"type": "column_list",
"column_list": {}
},
{
"object": "block",
"id": "cf507c16-71a4-49c2-b6c8-175c34c421e6",
"parent": {
"type": "page_id",
"page_id": "00cb99e8-3516-40d9-ab23-60773e070f0b"
},
"created_time": "2024-01-29T04:00:00.000Z",
"last_edited_time": "2024-01-29T13:09:00.000Z",
"created_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"last_edited_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"has_children": true,
"archived": false,
"type": "callout",
"callout": {
"rich_text": [
{
"type": "text",
"text": {
"content": "editor__imgbox1",
"link": null
},
"annotations": {
"bold": false,
"italic": false,
"strikethrough": false,
"underline": false,
"code": false,
"color": "default"
},
"plain_text": "editor__imgbox1",
"href": null
}
],
"icon": {
"type": "external",
"external": {
"url": "https://www.notion.so/icons/die1_gray.svg"
}
},
"color": "gray_background"
}
},
{
"object": "block",
"id": "82e0d066-8c52-48b7-8bd3-3a32e115616a",
"parent": {
"type": "page_id",
"page_id": "00cb99e8-3516-40d9-ab23-60773e070f0b"
},
"created_time": "2024-01-29T06:33:00.000Z",
"last_edited_time": "2024-01-29T06:33:00.000Z",
"created_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"last_edited_by": {
"object": "user",
"id": "a89a2abe-0d2b-47e7-a993-8bd35a07a79b"
},
"has_children": false,
"archived": false,
"type": "paragraph",
"paragraph": {
"rich_text": [],
"color": "default"
}
}
],
"next_cursor": null,
"has_more": false,
"type": "block",
"block": {},
"request_id": "fd87e87c-9c4b-4a9c-b2c3-f7414916b47d"
}
```
---
## 🪄 功能補充
----
### 利用 Callout Block 客製頁面元素 ✨
抓取 callout 的 text 並設為 `<div>` 的 `class`

----
### 利用 files 陣列屬性達成 RWD 換圖 ✨
因 files 可以上傳多張並可修改順序
可以列規則 ex: 有第二張時第二張則為 mobile 顯示

---
## 延伸問題 🧐
- 速度問題,Block 越多越慢 - 待尋找解決方式 ⏳
- 備份 - [Automating Backups with Notion's API](https://notionbackups.com/guides/automated-notion-backup-api#store-data)
- Schema size [官方建議](https://developers.notion.com/docs/working-with-databases) 最大 50KB
- 非自用 Intergretion 設定流程 ⏳
---
## 線上應用
- [Super](https://super.so/) 🤑 - No Code 架站 [數位時代介紹文](https://www.bnext.com.tw/article/77375/-nocode-web-super?)
- [NotionNext](https://github.com/tangly1024/NotionNext) - 中文開源專案,還有[精美文件](https://docs.tangly1024.com/about)
- [Nocode Scripts](https://www.nocode-scripts.com/notion-api-html) 🤑
- [Notion Blocks to html](https://blocks-to-html.com/)
---
## 其他發想 💡
- 活動上傳照片、留言
- 視覺化資料
- 小型商案/個人案
---
## Reference
[Notion 官方文件](https://developers.notion.com/docs/getting-started)
[Youtube 教學](https://youtu.be/h6AO-WYB_4c?feature=shared)
{"title":"使用 Notion 作為 Headless CMS 客製 Portfolio","slideOptions":"{\"transition\":\"slide\"}","description":"image.png","contributors":"[{\"id\":\"8b5887e6-2379-4766-a40e-2b7874e93ab1\",\"add\":54015,\"del\":35231}]"}