白屏排查思路
背景:白屏为什么值得单独排查
白屏是前端线上故障里最直观、也最容易被用户感知的一类问题。用户打开页面后看到的是一片空白,既没有内容,也没有明确的错误提示;业务侧看到的可能是转化率下降、投诉增加,监控侧却不一定马上出现接口错误。
它的难点不在于“页面没有渲染”这个结果,而在于原因可能分散在多个阶段:HTML 没拿到、静态资源加载失败、JavaScript 执行中断、框架挂载失败、接口长时间 pending、CSS 把内容隐藏,甚至是低版本 WebView 不兼容新语法。
排查白屏的核心思路是:先判断页面卡在哪个阶段,再针对该阶段收集证据,最后用最小改动恢复用户可见内容。
什么是白屏
白屏指用户进入页面后,在一段可感知时间内没有看到有效业务内容。它不一定等同于浏览器报错,也不一定等同于接口失败。
从前端链路看,一个页面从访问到可见,至少经历这些阶段:
- 浏览器请求 HTML。
- HTML 引用 JavaScript、CSS、图片、字体等静态资源。
- JavaScript 下载、解析并执行。
- 前端框架创建应用实例并挂载到 DOM 容器。
- 页面发起接口请求并拿到关键数据。
- 组件完成渲染,用户看到主要内容。
任何一个阶段中断,都可能表现为白屏。
白屏排查的总体方法
白屏排查不要一开始就猜原因。更稳妥的方法是按链路分层定位:
| 阶段 | 重点检查 | 常见原因 |
|---|---|---|
| HTML 加载 | 主文档状态码、响应内容、缓存策略 | HTML 404、网关错误、灰度路由异常 |
| 静态资源加载 | JS/CSS 是否 404、CDN 是否可用 | publicPath 错误、发版后资源被删除、CDN 回源失败 |
| 脚本执行 | Console 报错、语法兼容性、运行时异常 | 低版本浏览器不支持语法、未捕获异常、依赖缺失 |
| 应用挂载 | 根节点是否存在、框架入口是否执行 | #root 或 #app 不存在、挂载逻辑被条件阻断 |
| 数据依赖 | 首屏接口是否成功、是否有超时兜底 | 接口 pending、鉴权失败、异常状态没有降级 UI |
| 样式渲染 | DOM 是否存在但不可见 | display: none、高度为 0、样式覆盖、主题变量缺失 |
排查顺序建议从“浏览器事实”开始:Network、Console、Elements、Performance,再进入日志、监控和发版记录。
第一步:确认是加载失败还是渲染失败
白屏首先要区分两类情况:页面资源没有正常加载,还是资源加载成功但页面没有正常渲染。
打开浏览器 DevTools 后,优先看 Network 面板:
- 主文档是否返回
200。 - HTML 内容是否是预期页面,而不是网关错误页、登录页或空响应。
- JavaScript 和 CSS 是否出现
404、500、net::ERR_ABORTED、ERR_CONNECTION_RESET。 - 是否存在大量 pending 请求,尤其是首屏必需接口。
如果 HTML 或入口 JS 没有加载成功,问题通常在部署、网关、CDN、缓存或资源路径上。此时继续看组件代码意义不大。
如果 HTML、JS、CSS 都是 200,但页面仍然空白,就需要进入脚本执行和渲染阶段继续排查。
第二步:检查静态资源与缓存问题
发版后白屏最常见的原因之一,是 HTML 与静态资源版本不一致。
典型场景是:
- 用户浏览器缓存了旧的
index.html。 - 旧 HTML 引用了旧 hash 的 JS 文件,例如
app.a1b2c3.js。 - 服务端发版后只保留了新文件,例如
app.d4e5f6.js。 - 用户再次打开页面时,旧 JS 返回
404,应用入口无法执行。
这类问题的判断依据很明确:Network 面板里会看到入口 JS 或 chunk 文件 404,且文件名通常带 hash。
治理上要遵循两个原则:
- HTML 不做强缓存:
index.html应尽快拿到最新版本,避免引用过期资源。 - 静态资源可长期缓存但要保留历史版本:带 hash 的 JS/CSS 可以长缓存,但发版时不要立刻删除上一版本资源。
一个常见的 Nginx 配置思路如下:
location = /index.html {
add_header Cache-Control "no-cache";
}
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
真实项目里还需要结合发布系统保留最近几个版本的静态资源,这样即使用户拿到旧 HTML,也能下载到对应的旧 chunk。
第三步:检查 JavaScript 执行是否中断
如果资源都加载成功,但页面仍然白屏,Console 面板通常是下一站。
重点关注这些错误:
Uncaught SyntaxError:浏览器无法解析某段语法。ReferenceError:变量或依赖未定义。TypeError:访问了undefined或null上的属性。ChunkLoadError:动态 import 的分包加载失败。ResizeObserver loop limit exceeded:通常不一定致命,需要结合页面表现判断。
兼容性问题在移动端 WebView 中尤其常见。例如代码里使用了可选链、空值合并、较新的浏览器 API,但构建产物没有按目标浏览器转译,低版本环境会在解析阶段直接报错,后续框架挂载逻辑完全不会执行。
排查时要确认三件事:
- 构建目标是否覆盖实际用户环境。
- 第三方依赖是否被 Babel、SWC 或构建工具正确处理。
- 运行时异常是否被全局错误监控捕获。
前端应用至少应该接入基础错误采集:
window.addEventListener('error', (event) => {
reportClientError({
type: 'error',
message: event.message,
filename: event.filename,
line: event.lineno,
column: event.colno,
});
});
window.addEventListener('unhandledrejection', (event) => {
reportClientError({
type: 'unhandledrejection',
reason: String(event.reason),
});
});
仅有监控还不够。核心页面需要配合框架级兜底,例如 React 的 ErrorBoundary,避免单个组件异常导致整页不可用。
第四步:确认应用是否成功挂载
有时 Console 没有明显报错,资源也都正常,但页面依然没有内容。这时要检查应用挂载链路。
常见问题包括:
- HTML 中缺少根节点,例如没有
id="root"或id="app"的容器。 - 入口脚本没有执行到
mount或render。 - 权限、环境变量、灰度开关等条件判断提前
return。 - 微前端场景下子应用生命周期没有被主应用正确调用。
- SSR 或 hydration 失败后没有降级处理。
检查方式很直接:
- 在 Elements 面板看根节点是否存在。
- 在 Sources 中给入口文件或挂载函数打断点。
- 在关键生命周期加临时日志,确认执行路径。
- 对微前端应用,检查主应用注册、路由匹配和容器节点是否正确。
如果 DOM 中已经有业务节点,但页面看不见,就要转向样式问题:容器高度是否为 0、父元素是否 display: none、主题变量是否缺失、暗色模式样式是否把文字和背景变成同色。
第五步:排查首屏接口与状态兜底
很多白屏不是代码崩溃,而是页面一直停在“等待数据”的状态。
常见表现是:
- 首屏接口长时间 pending,页面没有超时 UI。
- 鉴权接口返回
401,但路由守卫没有跳登录页。 - 接口返回空数据,组件没有 empty state。
- 接口失败后 loading 没有关闭,用户看到空白或永久骨架屏。
这类问题的关键不是“接口一定不能失败”,而是接口失败时页面仍要有可解释的状态。
常见兜底策略包括:
| 问题 | 兜底策略 |
|---|---|
| 接口超时 | 设置超时时间,展示重试按钮或降级内容 |
| 鉴权失败 | 清理无效登录态,跳转登录页或展示无权限说明 |
| 数据为空 | 展示 empty state,而不是空 DOM |
| 局部模块失败 | 保留页面主体,只让失败模块展示错误卡片 |
| 配置接口失败 | 使用本地默认配置,避免阻断整个应用启动 |
首屏关键接口还应纳入监控,至少记录接口耗时、状态码、错误类型和页面路由,方便把“用户看到白屏”和“哪个接口异常”关联起来。
生产案例:发版后部分用户白屏
一个典型的生产场景是:某前端应用发版后,新用户访问正常,但一部分老用户反馈打开页面白屏。
排查过程可以按以下路径推进:
- 查看用户截图,确认页面没有业务内容,也没有错误提示。
- 让用户提供浏览器版本和访问时间,缩小影响范围。
- 在监控中发现白屏用户集中在发版后的短时间窗口。
- 复现时打开 Network,发现入口 HTML 返回
200,但某个带 hash 的 chunk 返回404。 - 对比发版记录,确认发布流程清理了旧版本静态资源。
这个问题的根因不是业务代码错误,而是缓存策略和发布策略不匹配:HTML 可能被浏览器或中间代理缓存,但对应的旧 chunk 已经被服务器删除。
修复方案通常分为两步:
- 止血:回滚或补回上一版本静态资源,让旧 HTML 仍能加载成功。
- 治理:调整缓存策略和发布策略,HTML 使用
no-cache,带 hash 的静态资源保留多个历史版本。
这个案例的价值在于提醒:白屏不一定发生在 JavaScript 逻辑内部,构建、缓存、CDN、发布系统都可能是根因。
线上白屏监控怎么做
依赖用户反馈排查白屏会很被动。生产环境中可以做主动检测。
一种简单方案是检测关键容器是否渲染出有效内容:
function detectWhiteScreen(): boolean {
const rootElement = document.querySelector('#root');
if (!rootElement) {
return true;
}
const hasVisibleContent = rootElement.children.length > 0 && rootElement.clientHeight > 0;
return !hasVisibleContent;
}
setTimeout(() => {
if (detectWhiteScreen()) {
reportWhiteScreen({
path: window.location.pathname,
userAgent: navigator.userAgent,
});
}
}, 5000);
更完整的方案会结合性能指标和错误日志:
- 使用
PerformanceObserver采集 FP、FCP、LCP。 - 采集
error、unhandledrejection和资源加载失败事件。 - 上报当前路由、构建版本、用户环境、网络类型。
- 对关键页面设置白屏率、JS 错误率、资源失败率告警。
需要注意,白屏检测不能只看根节点是否为空。某些页面首屏本来就依赖异步数据,短时间内容为空并不一定是故障。检测逻辑要结合页面类型、超时时间和业务关键节点设计。
面试中如何回答白屏排查
面试里回答白屏排查,重点不是背一串原因,而是体现系统化定位能力。
可以按这个结构回答:
- 先分阶段:HTML、静态资源、JS 执行、应用挂载、接口数据、CSS 渲染。
- 再讲工具:Network 看资源,Console 看错误,Elements 看 DOM,Performance 看首屏时序,监控看影响面。
- 然后给典型场景:发版后 chunk 404、低版本浏览器语法不兼容、接口 pending 没有兜底。
- 最后讲治理:缓存策略、错误监控、白屏检测、降级 UI、发布回滚和资源保留。
一个比较完整的回答可以是:
我会先判断白屏发生在哪个阶段。如果主文档或 JS/CSS 没加载成功,就重点看 Network、CDN、缓存和发布记录;如果资源正常,就看 Console 是否有运行时错误或兼容性问题;如果脚本没报错,再检查应用根节点、框架挂载和路由守卫;最后看首屏接口是否 pending,以及 loading、empty、error 状态是否有兜底。线上治理上会接入错误监控、资源失败监控和白屏检测,并配合 HTML no-cache、静态资源保留历史版本来降低发版后白屏风险。
总结
白屏排查的关键是把“页面空白”拆成可验证的链路问题。
- 先看资源是否加载成功,再看脚本是否执行成功。
- 资源正常时,继续检查应用挂载、接口依赖和样式可见性。
- 发版后白屏要重点关注 HTML 缓存、chunk 404、CDN 和发布策略。
- 生产环境不能只靠用户反馈,需要接入错误监控、资源失败监控和白屏检测。
- 好的治理目标不是保证永不出错,而是在出错时让页面有降级、有提示、可定位、可回滚。