这轮改动一开始只是想让首页那张莫奈背景图快一点。后来一路往下查,发现真正需要补的不是某一个组件,而是一个个人网站从“能打开”到“可以长期写、长期维护”中间缺的几段连接。
个人网站刚做出来的时候,很多问题都不明显。首页能打开,卡片能翻转,文章也能写;真正开始频繁打开、在手机上看、准备持续更新后,才会发现一些小地方一直在拖后腿:首屏图片慢,内容只能靠本地 Markdown,文章发布后还要等缓存,RSS 和 sitemap 也不知道有没有跟上。
这次没有重做视觉,也没有引入一套很重的架构。主要做的是把几个原来“暂时能用”的地方,改成能一直用下去的样子。
首页最慢的东西,往往就是最显眼的东西
首页背景是一张全屏铺开的画。它决定了进站第一眼的氛围,但也天然会变成首屏最大的资源。
之前的处理很直接:放一张大图,让它铺满屏幕。问题也很直接——浏览器先要下载比较大的原图,之后才能把首屏完整画出来。背景图不是普通配图,不能简单裁成很小的缩略图;一旦比例不对,整张画会被拉伸,网页里最容易被注意到的部分反而会变形。
最后没有走“换一张更小的图”这条路,而是保留全屏背景需要的比例,把资源整理成适合网页使用的 3840px 版本。文件体积从原先接近 7 MB 的量级降到了约 2.3 MB,同时仍然能覆盖大屏幕。
渲染方式也从普通图片换成了 Next.js 的 Image:
<Image
src={monetBackground}
alt=""
fill
preload
sizes="100vw"
quality={75}
className="object-cover"
/>
这里几个配置各自负责一件事:
fill + object-cover让图片始终按比例覆盖全屏,不会被强行拉伸;sizes="100vw"明确告诉浏览器,这张图就是整个视口宽度,避免它按错误尺寸挑选资源;preload把首屏背景放进更靠前的加载队列;quality={75}再压一层体积,但不把画面压得发糊。
这类优化很难只用一句“快了百分之多少”概括,因为网络、设备和缓存都会影响结果。但改完后,浏览器不再把原始大图当成普通静态文件慢慢等,首屏最关键的资源有了明确的优先级和尺寸信息。对这个网站来说,这比继续堆动画更值。
文章和“随心”,不应该长成两套页面
接着做的是“随心”。
一开始它只是一个想法:文章更适合完整的技术整理,但很多零碎的感受、读书记录、实习里的片段,并不值得为了凑一篇文章而硬写成长文。如果没有一个低压力的出口,这些东西通常就留在 Obsidian 里,然后慢慢被忘掉。
最容易的实现方式,是复制一份文章目录页和详情页,改改文案,再接一个 notes 目录。这样短期看很快,但后面一定会出问题:文章页修了 SEO,随心页忘了;文章页多了 RSS,随心页没有;两边的日期、目录、返回链接开始一点点不一致。
所以这次没有复制页面,而是先把内容抽成共同的 ContentCollection。文章和随心只是两类内容,底层都走同一套读取、排序、详情页和目录组件:
posts ─┐
├─ ContentCollection / JournalIndex / JournalEntry
notes ─┘
这样做的结果不是“代码更高级”,而是以后改一个小细节时,只需要改一次。文章和随心可以有不同的名字、入口和内容气质,但它们的阅读体验应该是一致的。
顺手也补了首页入口、RSS 和 sitemap。现在随心不是一个藏在某个路由里的页面,而是和文章一样会出现在内容流、订阅源和搜索引擎站点地图里。
静态缓存不是“内容发出去以后等一会儿”
内容站点很适合静态缓存。大多数读者看到的是同一篇文章,没必要每一次访问都重新去 CMS 拉一遍数据。问题在于,缓存如果只有“缓存一小时”,写作者的体验会很差:刚发布一篇随心,打开网站却还是空的,不知道是没发布成功,还是缓存还没过期。
现在的策略是两层:
- 正常情况下,文章和随心静态缓存一小时;
- Sanity 发布、更新或删除内容时,用 webhook 请求网站的 revalidate 接口,主动刷新相关页面。
刷新范围不只是一篇详情页,还包括首页、文章目录、随心目录、RSS 和 sitemap。这样不会出现“详情页已经是新内容,但首页卡片还是旧的”这种很难解释的状态。
开发环境则不保留这层缓存。写内容、改 schema 的时候直接读取最新数据,避免本地调试时因为一个空结果被缓存住,又以为是自己代码写错了。
这个过程里我比较在意的是:缓存应该让读者更快,而不是让作者更困惑。定时缓存解决访问成本,发布时失效解决更新时效,两件事最好分开处理。
从 Markdown 到 Sanity,不是把写作交给后台
有了“随心”之后,本地 Markdown 的问题开始变得明显。
Markdown 很适合在编辑器里写,也适合版本管理;但如果每次发布都要打开项目、建文件、写 front matter、提交 Git、等 Vercel 部署,它更像一个开发流程,而不是写作流程。尤其是随心,本来就应该足够轻。
于是把内容迁到了 Sanity,但没有把 Markdown 一次性扔掉。
Sanity 里定义的是一份统一的内容 schema:标题、URL 标识、发布日期、更新时间、摘要、标签、封面和正文。文章与随心共用这些基础字段,只用类型区分。原有 Markdown 通过迁移脚本先预览,再确认写入;网站读取 CMS 失败时,仍然会回退到本地 Markdown。
这个回退看起来有点保守,但我觉得很有必要。个人网站的内容不应该因为 CMS 临时连不上、环境变量配错,或者某次迁移漏了数据就全部消失。Sanity 是新的发布入口,Markdown 仍然是内容的本地备份和兜底。
最后把 Studio 嵌进了 /studio。以后写随心不用进 GitHub,也不用重新部署网站:打开后台,填写标题、URL、日期、摘要、标签和正文,发布即可。发布成功后 webhook 会通知网站刷新缓存,前台内容就会更新。
这不是在追求“像大网站一样有后台”,而是把写作这件事从部署流程里抽出来。内容值得有自己的入口。
SEO 不是关键词堆砌,是让内容有正确的出口
之前我对 SEO 的理解有点粗:页面能被搜到,应该就差不多了。实际补起来才发现,它更多是在做内容之间的边界说明。
这轮把几个基本的东西补齐了:
- 页面有稳定的 canonical 地址,避免同一内容被不同 URL 当成多份;
robots.txt指向站点地图;- sitemap 同时收录文章、随心和各自的详情页,并用发布日期或更新时间作为
lastModified; - RSS 把两类内容放进同一个订阅源,但仍保留“文章 / 随心”的分类;
- 日期展示统一到“年、月、日”,不把 CMS 的完整时间戳直接丢到阅读页面上。
这些东西读者平时不一定能直接看到,但它们决定了搜索引擎、RSS 阅读器和分享链接看到的是不是一个完整的网站。对个人站来说,SEO 不需要装得很复杂;至少应该让每一篇内容有唯一地址、准确标题、摘要、时间和可被发现的路径。
这轮改动最后留下了什么
回头看,最开始只是首页背景图加载慢。现在留下来的东西却不止一张压缩过的图片:
- 首页的最大资源有了更合理的加载方式;
- 文章和随心共用一套阅读与索引结构;
- 内容既能从 Sanity 发布,也保留本地 Markdown 作为兜底;
- 缓存有发布时的主动刷新,不再只能被动等待;
- RSS、sitemap、metadata 和日期格式都跟上了新的内容类型。
当然,网站还没到“什么都不用管”的程度。留言板的管理入口、CMS 内容的编辑体验、移动端细节,都还有继续修的空间。但这次之后,selfweb 至少不再只是一个一次性展示页:它开始有了适合持续写作、持续维护的基本节奏。
我现在比较喜欢这种改法——不追求一次把所有功能做满,而是每次遇到一个真实的不顺手,就顺着它往下看,找到应该补在哪一层。背景图慢,表面上是图片问题;继续往下,最后补到的是内容、缓存和发布链路。
个人网站大概就是这样慢慢长出来的。
作者:T | 汕头大学光电信息科学与工程 | AI Agent 方向 GitHub: github.com/iuyup