cover

使用 Notion 搭建博客

sorcererxw

为什么选择 Notion

想想我使用过的博客产品也有不少了,但是历数一下自己用过的博客,总有那么几点令我没法满意

  • 商业化博客平台代表 Medium: 无法自定义+体验不好
    • 网页编辑体验差
    • 无法自定义域名(免费账户)
    • 页面排版虽然不错,但是中文排版一般,而且还不支持代码块高亮
    • 数据不能直接导出 Markdown,迁移到到其他平台成本高
  • 静态博客代表 Hexo: 麻烦
    • 需要额外备份文章
    • 哪怕改了一个字都需要进行一次部署 (定时自动化部署也无法做到实时)
  • 博客系统代表 Wordpress: 令人操心
    • 需要同时部署 wordpress 和数据库,增加维护成本
    • 编辑器体验差
    • 找不到喜欢的主题(我又不会 PHP,没办法定制🤷‍♂️)

而 Notion 是到目前为止,我认为几乎完美的写作工具,而且 Notion 的文档也可以直接设置为公开,这样也能直接当作博客来使用

但是我需要:自定义域名,集成 Google Analytics,自定义界面,SEO...... 这样就需要自己去定制。刚好,Notion 的 API 是半公开的,我可以直接通过 API 来创建网页。这样一来,就可以把 Notion 当作和 Wordpress 一样的 CMS,来管理自己的博客。

让写博客的这件事情变的简单有趣

如果正好你平时就是将 Notion 作为自己的笔记应用,那么使用 Notion 管理博客的好处是显而易见的。

我可以在记笔记的同时,根据笔记内容撰写一篇博客;如果什么时候想进行修改一篇文章,也只需打开对应的页面,修改就好了。而且我也不需要去考虑数据的备份与图床,服务的稳定性,Notion 会帮我做好一切。

这样大幅度地降低了撰写博客的时间成本,只有简化流程,才能让你不断地坚持下去

再加上 Notion 丰富的组件,可以编写出相比传统的 Markdown 文档更加生动的页面

开发

获取 API

Notion 的 API 非常容易获得, 加载网页的过程中, 就能够直接在浏览器开发者面板中看到页面加载的 API 信息。

主要用到的三个 API

loadPageChunk: 获取一个页面的全部信息

getRecordValues: 获取单个 Block 信息

queryCollection: 获取 Notion 的集合组件数据,比如 table, list

编码

Notion 的页面 Block 是树状的,不过请求过来的数据是列表的形式的,需要在本地转成树形,然后再交给渲染函数进行渲染。

每一个 Block 都有一个 type 字段,通过这个字段可以决定到底是渲染出什么组件。所以整体上就是定义一个 NotionBlock 组件,让这个组件自己决定渲染什么内容。各个层次的 Block 也都将 child block 交给 NotionBlock 进行渲染就好了。

这样子一来就能够有效地解耦页面和组件,我只需要开发好我用得到的组件即可,未来如果有更多的需要,比如 Google Map, Twitter 的内嵌,只需要再适配一下,然后把 Block Type 注册到 NotionBlock 里面即可。

后端渲染

为了更好的 SEO 效果,将开发了一半的 React 应用使用 next.js 进行了重构。并且将 notion api 请求放在了服务器端,对于文章列表和文章进行了缓存,尽可能提高页面加载速度。

虽然服务端请求数据会让每次页面加载白屏时间变长;但是另一方面,因为直接在服务器进行访问,消除了 Notion 在国内访问速度慢的问题。

同时,直接在后端实时生成 sitemap.xml,提高 SEO 效率。

部署

我使用了香港的 Google App Engine, 直接部署 Node.js 项目非常方便,而且标准容器也是免费的。

为了更好的加载速度,使用了 Cloudflare 的 CDN 服务(懒得为了国内加速而去备案),速度还是可以接受的,国内首次直接加载能够做到 5 秒显示页面。

优化

网站可以访问了,整体速度虽然比不上之前使用的 Github Pages 静态博客,但是还是可以接受的。主要性能瓶颈在于同步请求 Notion API 和页面实时渲染,所以优化方向就是异步请求 API 并缓存数据和缓存渲染结果。

好在 Notion 的 API 可以获取到文章的最后修改时间,可以在后台监控文章变化,并及时进行更新缓存。

迁移

导入文档

Notion 自带 Markdown 导入工具

把自己之前的所有 markdown 文档导入到 Notion, 不过有几个注意点

  • Notion 不支持 Markdown 的多级 header, 仅仅支持一级#和二级##header, 这个做法和 Medium 是一样的, 需要对原来的一些文档进行修改。

    书写的时候, 也需要注意对文章进行适度分级, 虽然带了不便, 不过过度分级确实不是写文章的最佳实践。

  • Notion 不支持 Markdown 原生的 Table, 而是需要使用 Notion 的 collection 组件, 原本导入的数据结构会乱掉, 需要手动重新调整一下。
  • Notion 不支持内嵌 HTML,如果原文文档里面有内嵌 HTML,在 notion 上回变成一段普通的文字,而且 notion 也没有提供相应的组件来渲染 HTML (内嵌 codepen 可能是一种解决办法,但是比较麻烦)

    所以如果需要在博客上渲染,需要自己制定相应的规则,而我就用了 code 组件进行保存 HTML 代码

    if (language === 'HTML' && codeText.startsWith("<!--render-->")) {
         return <div dangerouslySetInnerHTML={{__html: codeText}}/>
    }

    通过对代码块进行标记,来让前端进行实时渲染。

使用表格管理文档

建立一个 Table Page, 将导入的文章全部拉入这个页面

建立相应的列

  • 标签 Tags
  • 发布日期 Date

    不同于 Notion 自带的创建日期和修改日期,自己定义一个日期可以自行修改,用于排序

  • 文章名字 name

    不同于标题,是用于 url 展示的,比如 blog.sorcererxw.com/post/build-blog-with-notion, 是展示一个名字,而不是一串 uuid

  • 是否发布 publish

    作为一个开关来控制文章是否被展示出来

建立视图

Notion 的 Table 有类似数据库的视图功能 view, 可以建立一个 view, 并为其设置 filter 和 sort,这样,这样一来,只需要在前端拉取这一个视图,就可以自动进行排序和展示相应的内容

Filter
Sort

至此,博客已经基本成型了。

总结

这一回痛下决心,花了一定的时间成本,但是一步到位开发完成个人博客(完美退烧,解决我在其他平台上遇到的大部分问题,甚至还拥有很多 Notion 独有的好处。未来只需要进行适度的维护就好了,从此不用再纠结到底是使用哪个博客平台了。

虽然还有很多可以定制的地方,可以让我通过 Notion 进行更多的配置工作,但是想想还是算了,最近也花了不少时间在这个上面了。自己的需求也基本上满足了,未来再说吧。