网站上线修复记录:3D 首页首访、模式隔离与全球边缘缓存
这次上线前修复让具备条件的桌面访问直接进入 Studio,隔离 Studio 与 Classic 的手动切换,压缩 3D 交付资产,并恢复多语言归档页的静态生成和全球边缘缓存。
这次上线前检查最初看起来只有两个问题:第一次打开域名会落到普通版首页;从普通版切回 3D 首页以后,页面又会长时间停在静态贴图上,迟迟不能操作。
真正排查下来,它们不是同一个 loader 的问题,而是首页模式状态、3D 资源交付、语言切换、Next.js 动态渲染和边缘缓存共同作用的结果。因此这次没有用“多显示一个 loading 动画”遮住等待,而是把首访链路从路由到首个有效 Three.js 帧重新梳理了一遍。
本次上线要点
- 在 WebGL、设备和网络条件允许时,桌面首页直接以 Studio 海报进入,不再先渲染可浏览的 Classic 首页再覆盖。
- Studio 与 Classic 的手动选择只在当前标签页运行时有效;切换语言不会串台,刷新后重新进行能力判断。
- 生产 GLB 从
9,620,604字节降至6,524,276字节,缩小32.18%,同时保持场景层级、镜头、材质和交互命名不变。 - 加载进度改为真实字节进度,
100%只代表首个有效 authored frame 已经接管海报,而不是“文件刚下载完”。 /zh、/en及博客、摄影、关于等顶级归档页恢复静态生成,并由 Vercel 边缘缓存直接返回。- Cloudflare 继续负责域名 DNS 和
img.huzejun.com的 R2 图片;网站 HTML 与 3D GLB 由 Vercel 直接交付,避免把两层 CDN 的职责混在一起。
两个首页必须是两个明确状态
Studio 首页不是 Classic 首页上方的一层视觉特效。它有自己的海报、Canvas、房间导航和交互状态;Classic 则是用户主动选择以及移动端、reduced motion、无 WebGL、低速网络或运行失败时的完整回退。
此前的问题在于模式选择没有一个足够窄、又能跨语言重挂载存活的状态边界。于是用户从 Classic 切到 Studio 后,再切换语言或触发组件重建,页面可能重新执行默认判断并回到 Classic。
现在手动模式只保存在当前页面运行时的 window 状态中:
- Studio 中切换中英文,仍然留在 Studio。
- Classic 中切换中英文,仍然留在 Classic。
- 硬刷新不会持久化选择,也不会写入 cookie、
localStorage、sessionStorage或 URL。
这种边界有意保持短暂。它足以防止同一次浏览中的模式串台,又不会让几个月前的一次选择永久覆盖今天的设备能力。自动回退也更精确:saveData、slow-2g 和 2g 默认进入 Classic;普通 3g 不再仅凭标签被拒绝。移动端、粗指针、reduced motion、无 WebGL、初始化失败和 context loss 仍会直接使用 Classic。
海报是首帧,不是等待页
3D 冷启动不能保证 Canvas 在 HTML 到达时已经可绘制,但这不意味着首页必须出现黑屏、长进度门或一份完整 Classic 内容作为临时垫层。
Studio 现在从同一 Blender 镜头导出的场景海报开始。顶栏导航和语言、Classic 控件立即可用;Three.js 在后台准备场景,只有首个有效帧完成以后才在原位接管。海报和 Canvas 使用同一构图,因此接管时不会发生镜头跳变。
进度也改为按网络实际接收字节计算。加载期间显示 1–99%,只有场景已经完成第一次有效绘制才进入 100%。这样“下载完成”“解析完成”和“可操作”不再被压缩成一个误导性的瞬间。
把 3D 冷启动拆成可以优化的阶段
生产场景仍然是同源 GLB,不依赖在线模型、纹理或 iframe。几何优化器只移除材质没有使用的顶点属性,最终删除了 594 组冗余属性;交付前后的 223 个 mesh、223 个 primitive、240,894 个三角形、29 个材质、21 张图像与纹理、379 个节点和 6 个镜头保持一致。
新的交付门槛包括:
- 目标文件不超过
7 MiB,硬上限为16 MiB。 - 估算 GPU 分配不得超过
128 MiB。 - 构建时校验 manifest、哈希、资产结构、交互根节点和许可记录。
网络端先发起一个 64 KiB Range 探测,并校验 206 与 Content-Range。标准环境并行获取剩余四段,资源受限环境使用两段;如果服务端返回完整 200,则直接复用响应体,不再重复下载。KTX2/Basis 初始化与资源请求并行进行,中断或语言重挂载时立即释放 loader,避免同一场景出现两套解码器和重复请求。
在一次干净、无缓存的受限浏览器测试中,旧实现大约 70 秒仍停在海报状态;新实现约 2.68 秒显示海报和可用控件,约 5.75 秒已经报告 26% 的真实进度,约 8.78 秒由可操作 WebGL 接管。这个数字不是所有设备的承诺,但它证明等待时间已经来自真实网络和解码工作,而不是模式重挂载、重复 loader 或错误的页面占位。
恢复静态首页和归档页
3D 文件不是全部瓶颈。上线后还发现,本应静态的多语言页面因为 locale cookie 和请求期 API 被标记为动态渲染。访客即使只打开普通博客或摄影归档,也可能跨区域回到某个函数节点,而不是直接命中最近的边缘缓存。
locale layout 现在为 zh 和 en 生成静态参数,在渲染前设置请求语言,并关闭不需要的 locale cookie。摄影归档的筛选继续由客户端 URL 状态管理,不再让服务端页面等待 searchParams。因此 /zh、/en、博客、摄影、书法、关于和履历等顶级页面都可以预渲染。
摄影与书法详情仍保留查询参数相关的上一张、下一张和返回筛选链接,因此这些详情路由没有被盲目强制静态化。性能优化不能以破坏导航语义为代价。
线上检查中,预热后的 /en 与 /en/photography 都返回 X-Nextjs-Prerender: 1 和 X-Vercel-Cache: HIT,同时不再设置 NEXT_LOCALE,也没有 private、no-cache 或 no-store。全球探测中,多数预热节点的 HTML TTFB 落在约 67–304 毫秒;南非节点在一次重试中约为 597 毫秒,说明边缘缓存解决了不必要的跨区计算,但真实公网距离和节点波动仍然存在。
上线前验证
这轮修改不是只以“本机能打开”作为完成标准。交付前执行了完整的内容、代码、3D 资产和生产构建检查:
- ESLint、TypeScript 和内容构建通过。
- 377 项自动化测试全部通过。
- Studio manifest、GLB、交互根节点和许可校验通过。
- Next.js 生产构建完成,共生成 406 个静态输出。
- 实际浏览器确认:首次 Studio 帧没有 Classic 内容闪现;Studio 切换语言仍是 Studio;Classic 切换语言仍是 Classic;每个 Studio 页面只有一个 Canvas。
- Vercel 多区域 HTML、完整静态资产和 GLB Range 请求均进行了线上复核。
这次修复留下的结论是:3D 首页的“加载快”不是只压缩一个模型。首屏状态必须诚实,模式边界必须稳定,资源下载与解码必须可取消,静态页面必须真正进入边缘缓存,回退条件也必须比“设备名称”更接近实际能力。只有这些环节同时成立,Studio 才是一个可以上线的首页,而不只是一段在开发机上表现良好的 3D 演示。

