写在前面
连续写了很多篇出去玩的博客,很久都没写关于开发的文章了。最近在空闲时间放弃了无聊的兴趣,拾起了吃灰很久的 Kindle,一拿一放之间,亚马逊已经终止了 Kindle 在大陆的服务。一开始是看了《足利女童失踪事件》,因为听播客 痴人之爱 的时候,听到了一期关于丰饶之海的节目,于是饶有兴趣的阅读了《春雪》和《奔马》。沉醉在三岛由纪夫华丽的辞藻中,然后开始尝试在 Kindle 中划线,才发现这个功能是这么的不好用。
于是开发一个基于 Kindle 书摘文件生成一个可读性应用的想法开始在我脑中扎根。但是 Kindle 在不同系统中生成的书摘 txt 文件甚至是采用不同语言格式的(逆天)。所幸 kindle-zhcn-clippings-to-json 对中文
My Clippings.txt
文件进行了解析,抱着把一个 Kindle “粘”到电脑屏幕上的想法,一个仿照 Kindle UI 的项目诞生了。GitHub: kindle-highlight ( 点个 star 再走)
DEMO: moonlight
遇到的问题
Nextjs 在此之前一直是我较为喜爱的一个框架,因为它事无巨细的包含了很多处理开发中的细枝末节的配置。App route 是它最新推出的模式,在之前的 SSR 和 静态生成的基础上,增强了 React server component 的能力。我虽然将这个版本激进地用到了工作中的新项目里,但涉及的内容最多只是到
fetch
和拆分服务器组件上。所以这个项目我给自己设定的需求也算是增强对它的使用。在 server component 中读取文件
因为 Kindle 的书摘是一个
My Clippings.txt
文件,需要在服务端对它进行读取转换为 JSON。本来以为和在 node 中类似的使用 :但是在执行时,这个文件已经被打包到 .next 中,搜索后才发现这是一个讨论已久的问题。这个问题存在很多种解法:
fetch + headers + protocal
因为
My Clippings.txt
文件在 public
中,使用 fetch
就可以请求到,但是在 server component 中使用 /My Clippings.txt
来发起 fetch
并不会拼接上当前的域名,于是需要通过 headers 函数从请求的 header 中获取域名。readFileSync + path.join(process.cwd(), "/public/My Clippings.txt")
如果使用
readFileSync
则需要通过 process.cwd()
获取当前进程的目录,在进行拼接。edge runtime ?
如果你的部署 All in Vercel 的话,还会遇到在 edge runtime 下如何读取本地本件的问题
虽然在这里写了这么多,最后我选择了在 next.config.js 将 My Clippings.txt 的内容转为一个环境变量来使用,这样在运行时就不会产生什么读取行为。
动态生成 og image
因为采用 Next 和长期使用 Twitte,总是会在这种地方有所追求。而且作为一个书摘项目,除了给自己看,分享给别人,共同谈论读书的乐趣也能给自己的阅读带来正向的提升。所以一张精美的小卡片必不可少!
route handler
Route handler 是 Next 13 新推出的特性,它取代了以往的
/api
目录(其实我觉得没差)。一开始我的目标是使用一个 /og
的 Route handler 来动态返回一个 image 数据。而在页面中导出的 metadata :opengraph-image
opengraph-image
和 twitter-image
是 Next13 新增的对 metadata 的支持,在对应 route 的目录下新建 opengraph-image.tsx
文件,即可接受路由参数并返回一个 image 文件,同时会自动在 metadata 中生成相应的链接。这里生成图片可以采用:next/server
中提供的ImageResponse
node-canvas
或@napi-rs/canvas
打包的 og image URL 变成了 http://localhost:3000
如果到了这里你使用 Vercel 部署则万事大吉,如果使用自己的服务器或第三方部署服务。那么你的 og:image 链接很有可能被打包为绝对的
http://localhost:3000/...
,要解决这个问题则需要配置一个 metadataBase
,但是在现在 build in everywhere 的情况下,固定一个域名可能并不是那么现实。所以可以动态获取当前访问的域名来设置。generateMetadata + generateStaticParams = 500
本来使用
My Clippings.txt
文件生成的网站,每次更新这个文件时会伴随一次重新部署,所以对书摘的详情页,采用了 generateStaticParams
静态生成。但在最外层的generateMetadata
中使用了 headers
函数,导致静态强制变成了动态,页面则直接 500,所以这里就面临一个三选一的问题:- run in vercel
- 放弃生成小卡片
- 放弃静态生成
我最后还是选择放弃静态生成,去掉了
generateStaticParams
之后,一切都变得正常起来了,但是我很讨厌这样的抉择。无法引入的 .node 包
因为
node-canvas
在 arm64 架构的机器下不兼容,所以采用了 rust 编译的 @napi-rs/canvas
,在使用时遇到了无法识别 .node
文件的报错。在设置中将它设置为外部则可解决。RSS 支持
在提供 RSS 支持的时候,我原本以为只能输出为纯文字,遇到了一个文字不能换行的问题,但是看了很多现存的 RSS feed,发现他们都是直接输出 html 结构的,所以直接采用拼接 html 结构来输入。
写在后面
另外这个应用采用了 zeabur 部署,这是一个和 Vercel 类似的部署平台,对比起自己负担一个服务器费用和管理 Workflow 来说它性价比极高,可以用来部署一些小玩意和 preview。