网上有很多 blog 的模板,不管是 nextjs,nuxt 还是 astro ,gatsby,vitepress,都存在一个问题:自己写完发布的blog,自己再看比较麻烦,不能很好的集成到笔记软件,像Obsidian或 Notion 中。另外博客模板一般用 markdown,在代码库中写blog 没有在 Notion 或 Obsidian 中编辑体验好。最近折腾用Notion 的数据库作为 blog 的 内容管理系统 (CMS),网站用 nextjs 搭建,这样在Notion中写好的 blog,设置属性后,在网站上就能看到,编辑和发布都方便。

1 创建表格

在根目录创建一个表格数据库,之后再创建一些列,然后是创建一些视图,Notion 的布局视图有这几种,一般作为CMS,表格、列表和画廊布局都不错

image.png

2 拿到Notion 私有集成的token

第一次使用,在Notion 个人集成中https://www.notion.so/profile/integrations 添加内部集成,步骤截图如下:

image.png

image.png

类型选择 Internal 内部类型

image.png

配置好权限后保存,拷贝 集成密钥 以备后面使用

还可以在设置连接中,快速的找到连接,并复制token

image.png

3 接口测试

Notion 的开发文档中有接口详细信息

https://developers.notion.com/

image.png

他们提供了postman 接口调试的集合

https://www.postman.com/notionhq/notion-s-api-workspace/collection/y28pjg6/notion-api

image.png

主要用到2个接口,一是 根据数据的id ,查询有哪些页面,用作列表,二是根据 page id 查询 blocks, 用于具体博客内容的渲染

image.png

4 选择 支持 SSR 或SSG 的工具搭建网站

nextjs 是比较成熟的 SSR 方案,但是 nextjs 在部署的时候要启动一个渲染服务,对服务器的性能消耗 比 SSG 工具生成的 静态资源要多。我这里选择 vitepress,

主要是根据 **vitepress theme blog pure** 修改的,主要是两个地方,一个是 博文的索引,查询博文的 在 Notion 上的 page ID,还有属性信息;二是 一篇博文在 Notion 中的 所有 block 块信息,用第二个接口。

 const apiHost = API_HOST
    const databaseId = process.env.DATABASE_ID;
    const notionToken = process.env.NOTION_TOKEN;
    const url = `${apiHost}/databases/${databaseId}/query`;

    const results = await fetch(url, {
        method: 'POST',
        headers: {
            'Authorization': `Bearer ${notionToken}`,
            'Content-Type': 'application/json',
            'Notion-Version': '2022-06-28'
        },
        body: JSON.stringify({
            "filter": {
                "property": "状态",
                "select": {
                    "equals": "发布"
                }
            },
            "sorts": [
                {
                    "property": "Last edited time",
                    "direction": "descending"
                }
            ]
        })
    }).then(res => res.json()).then(data => {
        return data?.results ?? []
    }).catch(error => {
        console.log('apierror')
        console.error(error)
        return error
    })

博文全文的内容,要用到 vitepress 中的动态路由,在做SSG 构建的时候,要生成所有博文,以静态文件的方式存放

// [id].paths.js

export default {
    async paths() {
        const apiHost = process.env.API_HOST
        const databaseId = process.env.DATABASE_ID
        const notionToken = process.env.NOTION_TOKEN
        const url = `${apiHost}/databases/${databaseId}/query`;

        const results = await fetch(url, {
            method: 'POST',
            headers: {
                'Authorization': `Bearer ${notionToken}`,
                'Content-Type': 'application/json',
                'Notion-Version': '2022-06-28'
            },
            body: JSON.stringify({
                "filter": {
                    "property": "状态",
                    "select": {
                        "equals": "发布"
                    }
                },
                "sorts": [
                    {
                        "property": "Last edited time",
                        "direction": "descending"
                    }
                ]
            })
        }).then(res => res.json()).then(data => {
            return data?.results ?? []
        }).catch(error => {
            console.error(error)
            return error
        })

        let pageBlock = {};

        for (const element of results) {
            const id = element.id
            const url = apiHost + `/blocks/${id}/children?page_size=100`;
            let blocks = await fetch(url, {
                method: 'GET',
                headers: {
                    'Authorization': `Bearer ${notionToken}`,
                    'Content-Type': 'application/json',
                    'Notion-Version': '2022-06-28'
                }
            }).then(res => res.json()).then(data => {
                console.log('get blocks success for pageid', id)
                return data.results
            }).catch(error => {
                console.log('apierror')
                console.error(error)
                return error
            })
            pageBlock[id] = blocks
        }

        return results.map((pkg) => {
            return {
                params: {
                    id: pkg.id,
                    title: pkg.properties.Title?.title[0]?.plain_text ?? '888',
                    blocks: pageBlock[pkg.id]
                },
                page: {
                    title: pkg.properties.Title?.title[0]?.plain_text ?? '888',
                    blocks: pageBlock[pkg.id]
                }
            }
        })
    }
} 

效果如下:

image.png

总结

Notion + Vitepress 搭建blog ,主要分两步,第一步在Notion 管理面板中新建 内部集成,设置权限,并保存token。 第二步用 SSG 工具 构建博客,并用 Nginx 代理生成的静态文件。 这样的组合,方便博文的编辑和收藏,也能根据需要定义blog 的主题样式。


today
906 声望41 粉丝