我是如何将 Notion 作为博客后端的 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ByteCat
V2EX    Notion

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

  •  2
     
  •   ByteCat 2023-02-01 16:52:20 +08:00 3869 次点击
    这是一个创建于 981 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我用 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/[email protected]/tw-emoji' ]} dark={isDarkMode} meta={['nick', 'mail']} requiredMeta={['nick', 'mail']} imageUploader={false} copyright={false} /> : null} // 放在这个结束标记前面 </> 

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

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

    CMS 具体是指什么?

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

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

    提前感谢!
    ByteCat
        5
    ByteCat  
    OP
       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
        6
    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
        7
    ByteCat  
    OP
       2023-02-26 19:24:23 +08:00
    @foyo 静态生成也不错,不过我图片都用 Cloudflare 缓存了,所以很快,Notion API 请求多了好像有限制。
    Anjhon
        8
    Anjhon  
       2023-05-09 16:55:32 +08:00
    这个我必须来推荐 NotionNext 一波,完美契合你的需求,博客功能完善,而且作者还在持续更新; GitHub 地址: https://github.com/tangly1024/NotionNext ;打个广告(我的博客地址):anjhon.top
    stobacco
        9
    stobacco  
       264 天前
    @Anjhon 请问你的博客模板是自己写的么
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5800 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 43ms UTC 06:17 PVG 14:17 LAX 23:17 JFK 02:17
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86