我是如何将 Notion 作为博客后端的

2023-02-01 16:52:20 +08:00
 ByteCat

我用 Notion 写博客已经一年了,主要是非常方便,想到什么可以先打个草稿,也不用像之前写 Markdown 一样要关心图片和附件的问题。

最近花了一两天终于把小博客打理得比较像样了,便把这折腾过程记录下来。

🎉 效果展示

选择网站框架

Notion 官方提供的页面,虽然效果是最好的,但给人的感觉还是笔记页面,而不是真正的「网站」,也不能添加自定义的功能,例如评论,要把 Notion 作为真正的 CMS 来用,可以通过 Notion API 获取数据(作为后端),而我们自己去实现一个前端。

为了减少重复造轮子,我选择了 https://github.com/transitive-bullshit/nextjs-notion-starter-kit ,因为这是一个采用了 Next.js 的框架,而我本人对 React 也略有了解,修改起来比较容易。

这个框架的使用也非常简单,只需要 fork 一份到自己的仓库,再修改 site.config.ts 中的网站设置,使用 Vercel 即可部署一个网站,如果你不怎么想折腾,到这里就差不多结束了。

从 Vercel 迁移到自建服务器

是的,虽然 Next.js 是 Vercel 所开源,对 Next.js 的构建部署也非常方便,为什么我还要选择自建呢?

自从去年 nextjs-notion-starter-kit 的作者加入了图片优化之后,我常常能收到 Vercel 的滥用警告。

而事实是,我的图片数量远没有那么多,我也很少重新构建项目(后来知道是因为每次请求了签名的文件链接,导致图片无法被缓存,下文有解决办法),但一直超过用量不是个好兆头,我也承担不起 Vercel 高昂的费用(相比起很多服务器来说),于是决定把博客迁移到自建服务器上。

因为之前没有接触过 Next.js ,查询了一下资料发现部署是比较简单的,nextjs-notion-starter-kit 使用的是 SSR ( Server Side Render ,服务端渲染),只要把服务器用 Node.js 跑起来就可以了。

然而,我对这个框架做了较多的修改,如果用之前的「 fork-sync-修改-部署」流程,很容易产生冲突,于是我想到了一种比较方便的办法:用 GitHub Actions 拉取最新代码,用我自己修改的文件覆盖原作者的代码,再打包成 Docker image 。我已经在 GitHub 开源: https://github.com/imbytecat/nextjs-notion-starter-kit-docker ,需要的可以自己 fork 修改。

自定义功能的超能力

由于前端都由我们自己掌控,要做一些功能的更改非常容易。

移除 GitHub 分享按钮

原版框架的页面右上角会添加一个指向开源项目的按钮,而且不能简单通过配置文件设置不显示。

在阅读代码之后发现,这个按钮封装成了一个组件,而且确实没有相关的设置来移除,只能通过删除代码解决:

  1. 删除 components/NotionPage.tsx#L23 处的导入
  2. 删除 components/NotionPage.tsx#L284 处的组件

这样重新构建后就不会显示分享按钮了。

更改图片为永久链接

参考了 WeijunDeng 的修改建议,不过因为时间较久,但目前其代码依然可以使用。

只需要在 lib/notion.ts#L42 处加入以下代码:

  if (recordMap && recordMap["signed_urls"]) {
    const signed_urls = recordMap["signed_urls"]
    const new_signed_urls = {} 
    for (const p in signed_urls) {
      if (signed_urls[p] && signed_urls[p].includes(".amazonaws.com/")) {
        console.log("skip : " + signed_urls[p])
        continue
      }
      new_signed_urls[p] = signed_urls[p]
    }
    recordMap["signed_urls"] = new_signed_urls
  }

这样图片就会变成永久链接,不会每次都带着签名请求 Notion API ,也能正确触发图片缓存,但不知道为什么原作者迟迟不合并这个请求。

更改文章 URL 路径为 UUID

使用 UUID 作为路径的原因很简单:

  1. 使用评论系统,UUID 有唯一确定性
  2. Notion 页面本来就有唯一对应的 UUID ,获取起来比较方便
  3. 不用自己想 slug ,减轻心智负担

完成后的效果:

https://www.imbytecat.com/2f3456133af0425da87539dd6a8b2379

方法也很简单,只需要将 lib/get-canonical-page-id.ts#L23 替换成:

    return getCanonicalPageIdImpl(pageId, recordMap, { uuid: true }).split('-').slice(-1).join('')

另外不要忘了在 lib/get-canonical-page-id.ts#L12 上方加一句 // eslint-disable-next-line @typescript-eslint/no-unused-vars ,否则过不了 ESLint 检查,不能构建成功。

添加评论系统

评论系统我用的是 Waline ,以前用过 Valine 还不错,但是有纯前端实现有安全问题,并且非常依赖 LeanCloud ,而 Waline 可以自建。

评论的实现比较粗糙,还没来得及调整样式,所以还存在一些问题,不过大概的思路可以讲一下。

如果使用 Waline ,需要先添加依赖:

yarn add @waline/client
yarn add sass

首先可以新建一个 components/Comment.tsx 作为我们的评论框组件,内容大概是这样:

import { init } from '@waline/client';
import '@waline/client/dist/waline-meta.css';
import '@waline/client/dist/waline.css';
import React, { useEffect, useRef } from 'react';

import type { WalineInitOptions, WalineInstance } from '@waline/client';

export type WalineOptions = Omit<WalineInitOptions, 'el'> & { path: string };

export const Waline = (props: WalineOptions) => {
  const walineInstanceRef = useRef<WalineInstance | null>(null);
  const containerRef = React.createRef<HTMLDivElement>();

  useEffect(() => {
    walineInstanceRef.current = init({
      ...props,
      el: containerRef.current,
    });

    return () => walineInstanceRef.current?.destroy();
  });

  useEffect(() => {
    walineInstanceRef.current?.update(props);
  }, [props]);

  return <div ref={containerRef} />;
};

然后在 components/NotionPage.tsx 使用就可以了:

import { Waline } from './Comment'
// ...
      {block.id.replace(/-/g, '') !== site.rootNotionPageId ?
        <Waline
          serverURL='https://waline.imbytecat.com'
          path={'/' + block.id.replace(/-/g, '')}
          emoji={[
            '//cdn.jsdelivr.net/gh/walinejs/emojis@1.1.0/tw-emoji'
          ]}
          dark={isDarkMode}
          meta={['nick', 'mail']}
          requiredMeta={['nick', 'mail']}
          imageUploader={false}
          copyright={false}
        /> : null}
// 放在这个结束标记前面
    </>

最终应该可以得到和 🎉 效果展示 差不多的样子。

2586 次点击
所在节点    Notion
8 条回复
CrazyCoolL
2023-02-02 11:56:46 +08:00
直接使用 Notion 本身其实就挺好的,我的个人主页: https://imllf.com
lifesimple
2023-02-09 10:41:09 +08:00
@CrazyCoolL 强啊 老哥能写篇文章分享下是怎么搭建的么,纯 notion 搭建?改过样式么 另外就是留言板输入中文容易失焦
CrazyCoolL
2023-02-09 17:30:11 +08:00
@lifesimple 对,纯 Notion 搭建,只有留言部分是嵌入了其他页面。(好像是有失焦问题,怪)
Laussan
2023-02-19 02:58:43 +08:00
马一个,小白想问几个问题:

CMS 具体是指什么?

Notion API 获取的是什么类型的数据?是会完整拉取到服务器端么?

跑这个的 vps 大概需要什么配置?

提前感谢!
ByteCat
2023-02-20 20:49:54 +08:00
@Laussan 可以看下 [Wikipedia 的说明]( https://zh.wikipedia.org/zh-cn/%E5%86%85%E5%AE%B9%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F),Notion API 可以获取 Notion 的所有数据,所以可以据此复刻一个 Notion 出来,也可以用 API 来实现一个 CMS ,我用的方案就是 [nextjs-notion-starter-kit]( https://github.com/transitive-bullshit/nextjs-notion-starter-kit),是一个基于 Next.js 的框架。
跑的话,配置要求不高,随便一个服务器应该都可以,嫌麻烦可以直接用 Vercel 的,甚至不需要服务器。
foyo
2023-02-26 01:38:46 +08:00
可以不购买 VPS ,直接用 GitHub Pages 部署,具体可以参考 https://xchb.fun/%E7%94%A8notiongithub-pages%E6%90%AD%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%8D%9A%E5%AE%A2
ByteCat
2023-02-26 19:24:23 +08:00
@foyo 静态生成也不错,不过我图片都用 Cloudflare 缓存了,所以很快,Notion API 请求多了好像有限制。
Anjhon
358 天前
这个我必须来推荐 NotionNext 一波,完美契合你的需求,博客功能完善,而且作者还在持续更新; GitHub 地址: https://github.com/tangly1024/NotionNext ;打个广告(我的博客地址😂):anjhon.top

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/912310

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX