我用 Notion 写博客已经一年了,主要是非常方便,想到什么可以先打个草稿,也不用像之前写 Markdown 一样要关心图片和附件的问题。
最近花了一两天终于把小博客打理得比较像样了,便把这折腾过程记录下来。
Notion 官方提供的页面,虽然效果是最好的,但给人的感觉还是笔记页面,而不是真正的「网站」,也不能添加自定义的功能,例如评论,要把 Notion 作为真正的 CMS 来用,可以通过 Notion API 获取数据(作为后端),而我们自己去实现一个前端。
为了减少重复造轮子,我选择了 https://github.com/transitive-bullshit/nextjs-notion-starter-kit ,因为这是一个采用了 Next.js 的框架,而我本人对 React 也略有了解,修改起来比较容易。
这个框架的使用也非常简单,只需要 fork 一份到自己的仓库,再修改 site.config.ts
中的网站设置,使用 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 修改。
由于前端都由我们自己掌控,要做一些功能的更改非常容易。
原版框架的页面右上角会添加一个指向开源项目的按钮,而且不能简单通过配置文件设置不显示。
在阅读代码之后发现,这个按钮封装成了一个组件,而且确实没有相关的设置来移除,只能通过删除代码解决:
这样重新构建后就不会显示分享按钮了。
参考了 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 ,也能正确触发图片缓存,但不知道为什么原作者迟迟不合并这个请求。
使用 UUID 作为路径的原因很简单:
完成后的效果:
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}
// 放在这个结束标记前面
</>
最终应该可以得到和 🎉 效果展示 差不多的样子。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.