ChunLong Blog

ChunLong Blog

LOADING

在 2 核 2G 小水管上跑一个"特效很多"的博客

2026-09-101031 字 · 约 3 分钟0 次阅读

我的服务器是一台 2 核 2G 内存、带宽小得可怜的云主机。而这个博客长满了"贵"的东西:全局粒子系统、Live2D 看板娘、Three.js 星海、滚动动效、四套粒子主题……

朋友听说这个组合时的表情,和你们现在想的一样。但这套东西跑起来其实不卡,秘密不是服务器强,而是每一层都做了取舍。这篇是完整的性能笔记。

先说架构:钱要花在刀刃上

整站是 Next.js 全栈 + SQLite(better-sqlite3),零外部服务——没有 Redis、没有独立数据库进程、没有对象存储。一个 Node 进程加一个几 MB 的数据库文件就是全部后端。对小服务器来说,少一个进程就少一份内存。

文章页是动态渲染的(每篇的阅读数要实时),动态渲染贵怎么办?nginx 微缓存:

proxy_cache micro;
proxy_cache_valid 200 5s;

同一个页面 5 秒内的重复请求由 nginx 直接回缓存,Node 只在缓存过期时干一次活。这个"安全阀"扛住了被分享到群里瞬间的脉冲流量。它是全站最重要的十行配置,任何优化都不许动它。

动效的预算制:每一帧都要交账

"特效很多"不等于"随时全开"。站里的动效遵守一套硬预算:

该停就停。 所有 Canvas 动画共用一条铁律:requestAnimationFrame 空闲即停。烟花的粒子炸完了、孔明灯升空了、瓶子里没有动画要放——rAF 循环就退出,等下一次交互再唤醒。没人看的时候,CPU 是零。

上限封死。 烟花粒子总量 1200,超出丢最旧;粒子文字的目标点钳制 1600;每一处"无限生成"的粒子都有天花板。

分辨率打折。 所有画布的 DPR 钳制 ≤2—— retina 屏上 3 倍分辨率带来的像素量是 4.5 倍,视觉差异却微乎其微。流体的模拟分辨率更是固定 128:模拟精度看的是手感,不是屏幕。

能预渲染的绝不每帧画。 烟花的星空背景预渲染到离屏 canvas,主循环只 drawImage;孔明灯的五种色相提前烘成 sprite。每帧省下的几百次 arc() 调用,攒起来就是低端机上的流畅。

// 静态星空:resize 时画一次,主循环只贴图
const skyCv = prerenderStars(w, h);
// tick 里:
ctx.drawImage(skyCv, 0, 0, w, h);

尊重系统设置。 prefers-reduced-motion 一开,粒子减半、动画降速、自动模式关闭——这不是可选项,是所有动效的出厂底线。

看板娘、Live2D 和懒加载

左下角的 Live2D 看板娘是全站最重的单个资产,所以它也是最后一个加载的:首屏内容渲染完、浏览器闲下来(requestIdleCallback)才开始拉模型文件。Three.js 星海同理——实验室页的 3D 场景 dynamic({ ssr: false }) 懒加载,不进任何首页的包。

图片统一过 sharp 压缩出 AVIF/WebP,文章封面按容器尺寸给 sizes;漂流瓶架懒加载到视口前 240px 才请求数据。"用户还没看到的东西就不该花用户的流量"。

一个反直觉的原则:视觉零损失

性能优化的常见套路是砍效果——粒子少一半、动画删一半。我给自己立了相反的规矩:视觉效果一个不许砍,只能砍"获得视觉效果的方式"。

  • 不减粒子数量,减每帧的计算(预渲染、对象复用);
  • 不删动画,改动画的调度(空闲即停、离屏暂停);
  • 不降内容质量,降传输体积(AVIF、按需加载、微缓存)。

IMPORTANT

页面切走时 visibilitychange 停掉所有 rAF,回来再恢复。标签页在后台烧电是用户卸载你的理由,不是性能报告里的数字。

复盘

小服务器教给我的核心思路是预算制:CPU 按帧交账、带宽按需支付、内存能省则省、缓存能挡就挡。特效的丰富度是设计问题,能不能流畅跑起来是工程问题——两件事从来就不冲突。

2 核 2G 还能塞下什么?我在考虑下一篇写写白噪音素材是怎么压到 1MB 的。

评论