性能、安全与可观测性问题记录
记录 Web Vitals 归因、安全攻防、监控 SDK 设计中踩过的坑,每个问题都是实际遇到过并解决掉的。
问题一:LCP 达标,INP 不达标
现象:LCP 1.8s 达标,INP P75 冲到 300ms+,用户反馈点击卡。
原因:输入发生时长任务正在占主线程——为 LCP 提前加载的大 JS/hydration 在交互时还没让出。
解决:按顺序修——① Performance 面板找 Long Task,用 scheduler.yield() 切片(输入可插队):while (tasks.length) { if (shouldYield()) await scheduler.yield(); doChunk(); };② 重计算挪 Worker(解析/压缩/diff);③ 减一次性 hydration 开销。注意切片拖慢总完成时间——牺牲吞吐换响应是显式取舍,用 INP P75 + 任务完成时间的对比数据自证净收益。
问题二:给 LCP 图加 preload,反而慢了 200ms
现象:<link rel=preload> 首屏大图,LCP 指标恶化。
原因:preload 的 URL 和实际渲染用的不一致(srcset 响应式图片)导致下载两次;或挤占了其他关键资源带宽;或优先级没提对。
解决:先做 LCP 子时段归因(TTFB / 资源发现延迟 / 加载时长 / 渲染延迟),卡在哪段修哪段。"资源发现延迟"长(CSS 链深层依赖)才用 preload;一般场景 fetchpriority="high" 打在 img 上更对症;preconnect 只给马上要用的跨域源。字体组合拳:preload + font-display: swap + size-adjust。
问题三:CSP 配了 nonce 还被 XSS
现象:script-src 'nonce-xxx' 上线后仍被注入执行。
原因:nonce/CSP 管脚本加载入口,管不住 DOM sink 注入——innerHTML = 恶意串 里的 <img onerror> 不需要新 script 标签。
解决:加 Trusted Types 把 innerHTML/eval 等 sink 改成只接受 TrustedHTML 类型,普通字符串直接抛错:
Content-Security-Policy: require-trusted-types-for 'script'; script-src 'nonce-xxx' 'strict-dynamic'
落地路径:report-only 灰度收 sink 清单 → 封装 createHTML 策略(DOMPurify 过滤)→ 三方库用隔离策略。富文本场景注意 mXSS:过滤后的 HTML 回填 DOM 再序列化可能变形绕过,要过滤后校验而不是只校验输入。
问题四:token 过期后 5 个并发 401,全链路登出
现象:access token 过期瞬间,页面 5 个请求各自触发 refresh,结果全部登出。
原因:刷新风暴——refresh token 轮转下旧的立即失效,后到的刷新用已作废的 token。
解决:单飞(singleflight)刷新 + 请求重放只允许一次:
let refreshInFlight: Promise<string> | null = null;
async function refreshOnce(): Promise<string> {
if (refreshInFlight) return refreshInFlight; // 其余 401 等同一个 Promise
refreshInFlight = fetch('/auth/refresh', { method: 'POST', credentials: 'include' })
.then((r) => { if (!r.ok) throw new Error(); return r.json(); })
.then(({ accessToken }) => accessToken)
.finally(() => { refreshInFlight = null; });
return refreshInFlight;
}
存储用内存 + HttpOnly Cookie(XSS 偷不走,CSRF 靠 SameSite=Strict 补);公共客户端授权走 OAuth 2.1 + PKCE;刷新失败广播登出事件并清理 in-flight 请求。
问题五:监控 SDK 接入后页面 FPS 掉了 5
现象:SDK 上线后性能监控自己成了性能问题。
原因:rrweb 全量录屏序列化、PerformanceObserver 回调风暴、同步上报阻塞 unload。
解决:分层采样(错误/慢请求全量,正常会话 1-5%);rrweb 只在错误会话开启;上报走 Worker 聚合压缩 + 攒批(3 秒或 20 条)+ sendBeacon(unload 不阻塞),>64KB 用 fetch keepalive 兜底——双通道会重复,事件带 uuid 服务端幂等去重;所有采集代码 try/catch、开关可远程下发一键关闭。
