← 返回文章列表
025 — 前端 · #浏览器 API #React #Astro #踩坑 · 2026-08-17 · 4 MIN READ

踩坑笔记:crypto.randomUUID 在 HTTP 裸 IP 站点上直接不存在

这是排障那两天里最便宜的一个 bug:定位十分钟,修复十行,但症状是「整个聊天窗从页面上消失」,排场很大。

现象

静态站部署到 ECS 之后,首页打开,右下角那个轨道悬浮球不见了。聊天窗是这个博客唯一的 React 岛屿,Astro 用 client:idle 水合——页面 HTML 正常渲染,但岛屿一注水合就直接崩掉,连入口按钮都没留下。

打开 DevTools,Console 里一行红字:

plaintext
TypeError: crypto.randomUUID is not a function

根因:安全上下文

crypto.randomUUID() 不是哪里都有。MDN 上写得很清楚:它只在**安全上下文(Secure Context)**中暴露。安全上下文的判定规则大致是:

  • HTTPS 页面 ✅
  • localhost / 127.0.0.1 ✅(浏览器对本地开发网开一面)
  • file:// 各浏览器不一,别指望
  • HTTP + 域名或裸 IP

我的站当时还在等备案,临时用 http://47.108.112.97 裸 IP 访问——正好踩在 ❌ 那一格。此时 crypto.randomUUIDundefinedcrypto.randomUUID() 等于调用一个 undefined,直接 TypeError。

最坑的是这个异常的时机:它发生在岛屿水合阶段(React 组件首次执行),不是用户交互时。也就是说任何访客打开首页都会白屏掉聊天功能,无一例外。

为什么本地永远正常

本地开发跑的是 http://localhost:4321。localhost 在浏览器眼里是安全上下文,crypto.randomUUID 好好地在那儿。所以从开发到构建到 preview,全程无感,直到真机访问才炸。

这就是典型的环境差 bug:代码没错,是运行环境的 API 面不一样。

修复:带回退的 genUuid

ts
// blog-frontend/src/components/chat/useChatHistory.ts
function genUuid(): string {
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
    return crypto.randomUUID();
  }
  return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) =>
    (Number(c) ^ ((Math.random() * 16) >> 0)).toString(16)
  );
}

先探测再调用;探测不到就退回手写的 UUID v4 模板替换。回退版本用 Math.random(),密码学强度不够,但这个 ID 的用途只是给会话起个名字,撞了也无所谓——够用就行,别为了回退路径引入依赖。

排查套路:环境差问题怎么查

这个 bug 给我的方法论沉淀比修复本身值钱。以后遇到「本地正常、线上崩」,按这个顺序自查:

  1. URL 差异:协议(http/https)、host(localhost/IP/域名)、端口。这一次死在协议+host 的组合上。
  2. API 的上下文要求:Web Crypto、Service Worker、Clipboard、摄像头麦克风,都有一串安全上下文限制,查 MDN 的「Secure context only」标记。
  3. 水合时机:Astro/岛屿架构下,组件代码在浏览器里重新执行,SSR 时 Node 环境有的 API 浏览器里未必有,反之亦然。

以及一个工程上的补救:上线检查清单里从此多了一条「用真实访问方式(域名/IP + 最终协议)打开页面验证」,不再拿 localhost 当验收环境。毕竟 HTTPS 证书配上之后这个回退可能一辈子用不到,但检查清单会一直留下去。

Comments