本站是 Bojin Li 的个人网站。历史年份技术栈2019GitHub Pages + Jekyll + Disqus,Huxpro 主题2020Vercel + Jekyll + Disqus,Huxpro 主题2021Vercel + React 17 + Tailwind CSS + Vite + TypeScript2022Vercel + Next.js 12 + Notion API + Giscus + p5 + three.js2024Vercel + Next.js 14/15 + Tailwind CSS + shadcn/ui2026(当前版本,Haku)Cloudflare Workers + TanStack Start + Vite + Tailwind CSS + Durable Objects关于 HakuHaku,参考自 Yohaku。延续了 Yohaku 对阅读的重视,包括留白、纸张和细节动效带来的感觉,也有着同样完美的富文本文章渲染。至于 Haku 的名字,一方面保留了 Yohaku 的一部分,另一方面,Haku 在日语里恰好也是我名字里「帛」字的读音。Yohaku 是基于 Next.js 的,而本站最终部署在 Cloudflare Workers 上,且需要针对中国大陆特殊的网络环境做尽可能多的优化。所以本站使用了 TanStack Start、TanStack Router 和 Vite,并引入了 Durable Objects,这样在 Worker 上就能尽可能深入底层地优化 SSR 性能,也避开了 Next.js 的臃肿,以及它在非 Vercel 平台上的不确定性。后端方面,Yohaku 配套的很多功能本站用不上,而 Haku 又有不少自己的定制和优化,所以参考 Yohaku 重写了一个 CMS,在协议上尽可能与 Yohaku 配套的 mx-space 保持一致。整体架构在 monorepo 里,分成 Web、Core、Admin 三个应用。应用职责技术部署Web公开网站,包括主页、文章和独立页面React、TanStack Start / Router、Vite、Tailwind CSSCloudflare WorkersCore内容、媒体、身份、评论等后端服务NestJS、Fastify、Drizzle、PostgreSQL、Redis自建服务器,Dokploy 管理Admin内容管理后台React、Vite、CodeMirror、Haklex / Lexical自建服务器,Dokploy 管理三个应用之间用共享的 TypeScript 和 Zod 契约来对接,契约在请求和响应的边界上也会校验数据。服务端渲染Haku 使用服务端渲染。第一次打开页面的时候,Worker 会先根据路由和语言取到需要的数据,生成带着内容的 HTML,再以流的形式交给浏览器,让内容不用等脚本加载完就能先看到。接着浏览器会做 hydration,把服务器已经生成好的页面接管过来,让导航、菜单、评论这些交互开始工作。之后在站内跳转,就由 TanStack Router 来处理,只按需读取下一个页面要用的数据,不用每次都重新加载整份文档。为了让服务端渲染和浏览器接管能接得上,每个服务端请求都有独立的 Router、QueryClient 和 Jotai store,浏览器里也各有一份,所以不同读者的数据不会串在一起。入场动画则跟着路由一起处理,首次打开、普通跳转和前后退恢复各有各的行为,预取数据或者后台刷新的时候,页面也不会无缘无故重新播放一遍动画。缓存站点设置、导航信息和首页的各类汇总,这些内容对所有读者都是一样的,因此适合共享。它们会以公开快照的形式保存在 Cloudflare 的 Durable Objects 里,同一份数据的并发读取会被合并成一次,快照过期之后,则会先把保留的旧内容返回给读者,同时在后台去更新(stale-while-revalidate),减少读者等待。另外有新内容发布之后,旧快照会被标记为过期,缓存的更新还带着版本检查,一个更早发起的请求即使晚到了,也不能把旧数据写回去覆盖新的。文章正文不进入这层共享缓存,每次都是从已发布的内容里读取,HTML 和服务端函数的响应也保持私有。首页上展示的各种活动和统计数据也是类似的思路,它们由后端定期汇总好,读者打开首页的时候,不需要再临时去请求外部服务,外部服务偶尔慢一点,也不会拖慢正文的阅读。访问速度优化Haku 在访问速度上做了这些处理。站点启动数据尽可能缩小,只传导航和当前页面需要的信息,不夹带无关页面的内容。路由的 loader 和查询缓存衔接起来,减少同一次导航里的重复读取。富文本、地图和 3D 这些比较重的依赖,按使用场景拆开,需要的时候才加载。正文用到的字体、语言和渲染模块提前准备好,免得刷新和 hydration 时出现不一致。背景和 3D 的动效加了调度,页面隐藏或者读者开启了减少动效时会暂停或者降级成静态,尽量不去占用阅读的资源。此外,Haku 在 Chrome 开发者工具的网络受限模式下反复测试了 SSR 和首屏,并引入了 Sentry 来定位线上出现的错误和性能问题。
本站是 Bojin Li 的个人网站。
历史
年份
技术栈
2019
GitHub Pages + Jekyll + Disqus,Huxpro 主题
2020
Vercel + Jekyll + Disqus,Huxpro 主题
2021
Vercel + React 17 + Tailwind CSS + Vite + TypeScript
2022
Vercel + Next.js 12 + Notion API + Giscus + p5 + three.js
2024
Vercel + Next.js 14/15 + Tailwind CSS + shadcn/ui
2026(当前版本,Haku)
Cloudflare Workers + TanStack Start + Vite + Tailwind CSS + Durable Objects
关于 Haku
Haku,参考自 Yohaku。延续了 Yohaku 对阅读的重视,包括留白、纸张和细节动效带来的感觉,也有着同样完美的富文本文章渲染。至于 Haku 的名字,一方面保留了 Yohaku 的一部分,另一方面,Haku 在日语里恰好也是我名字里「帛」字的读音。
Yohaku 是基于 Next.js 的,而本站最终部署在 Cloudflare Workers 上,且需要针对中国大陆特殊的网络环境做尽可能多的优化。所以本站使用了 TanStack Start、TanStack Router 和 Vite,并引入了 Durable Objects,这样在 Worker 上就能尽可能深入底层地优化 SSR 性能,也避开了 Next.js 的臃肿,以及它在非 Vercel 平台上的不确定性。
后端方面,Yohaku 配套的很多功能本站用不上,而 Haku 又有不少自己的定制和优化,所以参考 Yohaku 重写了一个 CMS,在协议上尽可能与 Yohaku 配套的 mx-space 保持一致。
整体架构
在 monorepo 里,分成 Web、Core、Admin 三个应用。
应用
职责
技术
部署
Web
公开网站,包括主页、文章和独立页面
React、TanStack Start / Router、Vite、Tailwind CSS
Cloudflare Workers
Core
内容、媒体、身份、评论等后端服务
NestJS、Fastify、Drizzle、PostgreSQL、Redis
自建服务器,Dokploy 管理
Admin
内容管理后台
React、Vite、CodeMirror、Haklex / Lexical
自建服务器,Dokploy 管理
三个应用之间用共享的 TypeScript 和 Zod 契约来对接,契约在请求和响应的边界上也会校验数据。
服务端渲染
Haku 使用服务端渲染。第一次打开页面的时候,Worker 会先根据路由和语言取到需要的数据,生成带着内容的 HTML,再以流的形式交给浏览器,让内容不用等脚本加载完就能先看到。
接着浏览器会做 hydration,把服务器已经生成好的页面接管过来,让导航、菜单、评论这些交互开始工作。之后在站内跳转,就由 TanStack Router 来处理,只按需读取下一个页面要用的数据,不用每次都重新加载整份文档。
为了让服务端渲染和浏览器接管能接得上,每个服务端请求都有独立的 Router、QueryClient 和 Jotai store,浏览器里也各有一份,所以不同读者的数据不会串在一起。入场动画则跟着路由一起处理,首次打开、普通跳转和前后退恢复各有各的行为,预取数据或者后台刷新的时候,页面也不会无缘无故重新播放一遍动画。
缓存
站点设置、导航信息和首页的各类汇总,这些内容对所有读者都是一样的,因此适合共享。它们会以公开快照的形式保存在 Cloudflare 的 Durable Objects 里,同一份数据的并发读取会被合并成一次,快照过期之后,则会先把保留的旧内容返回给读者,同时在后台去更新(stale-while-revalidate),减少读者等待。另外有新内容发布之后,旧快照会被标记为过期,缓存的更新还带着版本检查,一个更早发起的请求即使晚到了,也不能把旧数据写回去覆盖新的。
文章正文不进入这层共享缓存,每次都是从已发布的内容里读取,HTML 和服务端函数的响应也保持私有。
首页上展示的各种活动和统计数据也是类似的思路,它们由后端定期汇总好,读者打开首页的时候,不需要再临时去请求外部服务,外部服务偶尔慢一点,也不会拖慢正文的阅读。
访问速度优化
Haku 在访问速度上做了这些处理。
此外,Haku 在 Chrome 开发者工具的网络受限模式下反复测试了 SSR 和首屏,并引入了 Sentry 来定位线上出现的错误和性能问题。