#建站
25 篇
把一座 Three.js 浮岛搬进静态站
浮岛页只下载自己那一份脚本。场景在运行时现算,Three.js 与整个三维世界都留在这一页的 chunk 里,首屏 HTML 里只有一个空舞台。
不建模也能有体积:手工三角化一座浮岛
岛上的岩石不是从建模软件导出的,是五层不规则环带在运行时拼出来的。顶点色加平面着色,就足够让一块几何体看起来像被凿开的石头。
一千二百个实例:把花园压进几次绘制
草叶六百、花一百二、苔石八十五、灌木四十二 —— 花园的密度不靠减数量,靠 InstancedMesh 把同一种东西合并成一次绘制。
一个场景的光:三种灯与只算一次的阴影
半球光给环境色,主光给方向与阴影,轮廓光把岛从背景里拉出来;其余六个灯笼只是点光。阴影贴图只算一次,因为它照的东西一动不动。
两个着色器做水:水洼、溪流与瀑布
岛上没有水面模拟,也没有反射。两个 ShaderMaterial 分别负责平躺的水与竖直落下的水,共用一组 time 与 night 变量。
3D 的加载预算:分包、可见才加载、以及什么时候该停下
一个 3D 页面有三笔成本 —— 下载、首帧编译、每帧绘制。三笔都要单独设预算,任何一笔失控都会让「好看」变成「卡」。
优雅关闭:进程退出方式决定用户看到 502 还是正常响应
收到终止信号立刻 exit 会切断在途请求。正确顺序是停止接收新连接、等在途请求跑完、再释放资源、最后退出。
HTTP 缓存:Cache-Control 与 ETag 各管一半
Cache-Control 决定「要不要发请求」,ETag 决定「发出去之后能不能省下响应体」。把两者混谈是缓存问题的根源:改一个参数不会同时修好两侧。
iframe sandbox:它收窄能力,不是加固
sandbox 属性默认剥夺一切能力,靠 allow-* 逐项放回。弄反方向是常见错误:加上 sandbox 之后脚本全不能跑了,因为那是默认值。
语义化版本:主版本号的承诺是「不会静默改变行为」
MAJOR 不是「改动很大」,而是「有破坏性变更」。把重构当主版本号、把破坏性变更藏在次版本号,都会让下游的自动升级变成事故。
技术债的利息长什么样
技术债不是「代码写得丑」,而是「以后每次改动都要多付的那部分」。判断标准是可测量的:改一行要动几个文件、跑多久测试、有几处需要同时改。
URL 编码的三个陷阱:空格、加号与双重编码
查询串里的 + 是空格,路径里的 + 是加号;同一个字符在路径与查询里的编码规则不同;编码两次会得到看似正常却永远匹配不上的值。
一致性哈希:加一台机器为什么不用重排所有键
取模分片在机器数变化时几乎搬走全部数据。把哈希空间首尾相接成一个环,键归顺时针第一台机器,加节点只影响一段区间 —— 代价是需要虚拟节点。
静态站的缓存头:带哈希的永久,HTML 一律不缓存
缓存策略只取决于文件名是否随内容变化,与文件大小无关。HTML 长缓存会让用户拿到旧入口、指向已删除的旧 chunk,是静态站最常见的线上白屏。
WebAssembly 值得用的场合,比宣传里少得多
WASM 的收益来自「计算密集 + 大批量数据 + 少的跨界调用」。把一个 O(n) 的 JS 循环改写成 WASM,通常只会得到更慢的加载和更难调的栈。
不用框架的 i18n:三语站点手写路由的代价
这个站点的手写 i18n 没用框架自带的配置。说说为什么,以及代价具体出现在哪里——两次 404,和一半没翻的文章。
纯静态站的全文搜索:构建期索引,运行时零后端
静态站做搜索不一定非得接 Algolia。把索引放到构建期生成,搜索完全跑在浏览器里,代价只有构建慢几秒。
1059 项断言,零测试框架:纯逻辑必须零 import
我的自检是 node scripts/selftest.ts 直接跑的,没有测试框架也没有依赖。代价是纯逻辑模块一个相对 import 都不能有,而这个代价换来了代码形状。
head 只有一个来源:SiteHead、JSON-LD 转义与 hreflang
三个 HTML 外壳共用一个组件输出全部 head 标签,数据在 server 层组装。以及一个必须记住的细节:JSON-LD 里的尖括号要转义成反斜杠 u003c。
静态站上线不是「把文件夹传上去」:缓存、压缩、以及诚实的 404
纯静态托管的坑几乎全在响应头上。哪些文件能永久缓存、哪些一秒都不能缓存、以及为什么静态站绝对不能做 SPA 式回退。
@/ 别名的三处配置:Vite、TypeScript 与 SCSS 缺一不可
全项目禁用深层相对引入,代价是别名要在三个地方各配一次。少配一处,症状分别是编辑器报错、构建失败、样式静默丢失。
zod 做 frontmatter 契约:把文章格式变成构建期错误
Content Collections 的 schema 是唯一真相。标题超 120 字、日期写错、标签写成字符串,都在构建期直接失败,而不是线上少一页。
为什么我又搭了一个个人主页
不是没有地方写东西,而是没有一个地方能同时放文章、小游戏和工具。顺便说说这次的技术选型为什么是 Astro 而不是 VitePress。
零配置注册表:新增一个游戏等于新建一个目录
列表、详情路由、首页精选、sitemap 全部由 import.meta.glob 自动发现。代价是必须接受「字面量相对路径」这条 Vite 的硬规则。
视图不存逻辑:把数据装配全部收进 src/server
页面里只剩路由导出与组件引入,取数、拼路径、算日期全部移到 src/server。说说这么分的收益,以及它逼出来的一个类型约束。
