白屏排查思路

17 分钟

背景:白屏为什么值得单独排查

白屏是前端线上故障里最直观、也最容易被用户感知的一类问题。用户打开页面后看到的是一片空白,既没有内容,也没有明确的错误提示;业务侧看到的可能是转化率下降、投诉增加,监控侧却不一定马上出现接口错误。

它的难点不在于“页面没有渲染”这个结果,而在于原因可能分散在多个阶段:HTML 没拿到、静态资源加载失败、JavaScript 执行中断、框架挂载失败、接口长时间 pending、CSS 把内容隐藏,甚至是低版本 WebView 不兼容新语法。

排查白屏的核心思路是:先判断页面卡在哪个阶段,再针对该阶段收集证据,最后用最小改动恢复用户可见内容

什么是白屏

白屏指用户进入页面后,在一段可感知时间内没有看到有效业务内容。它不一定等同于浏览器报错,也不一定等同于接口失败。

从前端链路看,一个页面从访问到可见,至少经历这些阶段:

  1. 浏览器请求 HTML。
  2. HTML 引用 JavaScript、CSS、图片、字体等静态资源。
  3. JavaScript 下载、解析并执行。
  4. 前端框架创建应用实例并挂载到 DOM 容器。
  5. 页面发起接口请求并拿到关键数据。
  6. 组件完成渲染,用户看到主要内容。

任何一个阶段中断,都可能表现为白屏。

白屏排查的总体方法

白屏排查不要一开始就猜原因。更稳妥的方法是按链路分层定位:

阶段重点检查常见原因
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 是否出现 404500net::ERR_ABORTEDERR_CONNECTION_RESET
  • 是否存在大量 pending 请求,尤其是首屏必需接口。

如果 HTML 或入口 JS 没有加载成功,问题通常在部署、网关、CDN、缓存或资源路径上。此时继续看组件代码意义不大。

如果 HTML、JS、CSS 都是 200,但页面仍然空白,就需要进入脚本执行和渲染阶段继续排查。

第二步:检查静态资源与缓存问题

发版后白屏最常见的原因之一,是 HTML 与静态资源版本不一致。

典型场景是:

  1. 用户浏览器缓存了旧的 index.html
  2. 旧 HTML 引用了旧 hash 的 JS 文件,例如 app.a1b2c3.js
  3. 服务端发版后只保留了新文件,例如 app.d4e5f6.js
  4. 用户再次打开页面时,旧 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:访问了 undefinednull 上的属性。
  • 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" 的容器。
  • 入口脚本没有执行到 mountrender
  • 权限、环境变量、灰度开关等条件判断提前 return
  • 微前端场景下子应用生命周期没有被主应用正确调用。
  • SSR 或 hydration 失败后没有降级处理。

检查方式很直接:

  • 在 Elements 面板看根节点是否存在。
  • 在 Sources 中给入口文件或挂载函数打断点。
  • 在关键生命周期加临时日志,确认执行路径。
  • 对微前端应用,检查主应用注册、路由匹配和容器节点是否正确。

如果 DOM 中已经有业务节点,但页面看不见,就要转向样式问题:容器高度是否为 0、父元素是否 display: none、主题变量是否缺失、暗色模式样式是否把文字和背景变成同色。

第五步:排查首屏接口与状态兜底

很多白屏不是代码崩溃,而是页面一直停在“等待数据”的状态。

常见表现是:

  • 首屏接口长时间 pending,页面没有超时 UI。
  • 鉴权接口返回 401,但路由守卫没有跳登录页。
  • 接口返回空数据,组件没有 empty state。
  • 接口失败后 loading 没有关闭,用户看到空白或永久骨架屏。

这类问题的关键不是“接口一定不能失败”,而是接口失败时页面仍要有可解释的状态

常见兜底策略包括:

问题兜底策略
接口超时设置超时时间,展示重试按钮或降级内容
鉴权失败清理无效登录态,跳转登录页或展示无权限说明
数据为空展示 empty state,而不是空 DOM
局部模块失败保留页面主体,只让失败模块展示错误卡片
配置接口失败使用本地默认配置,避免阻断整个应用启动

首屏关键接口还应纳入监控,至少记录接口耗时、状态码、错误类型和页面路由,方便把“用户看到白屏”和“哪个接口异常”关联起来。

生产案例:发版后部分用户白屏

一个典型的生产场景是:某前端应用发版后,新用户访问正常,但一部分老用户反馈打开页面白屏。

排查过程可以按以下路径推进:

  1. 查看用户截图,确认页面没有业务内容,也没有错误提示。
  2. 让用户提供浏览器版本和访问时间,缩小影响范围。
  3. 在监控中发现白屏用户集中在发版后的短时间窗口。
  4. 复现时打开 Network,发现入口 HTML 返回 200,但某个带 hash 的 chunk 返回 404
  5. 对比发版记录,确认发布流程清理了旧版本静态资源。

这个问题的根因不是业务代码错误,而是缓存策略和发布策略不匹配: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。
  • 采集 errorunhandledrejection 和资源加载失败事件。
  • 上报当前路由、构建版本、用户环境、网络类型。
  • 对关键页面设置白屏率、JS 错误率、资源失败率告警。

需要注意,白屏检测不能只看根节点是否为空。某些页面首屏本来就依赖异步数据,短时间内容为空并不一定是故障。检测逻辑要结合页面类型、超时时间和业务关键节点设计。

面试中如何回答白屏排查

面试里回答白屏排查,重点不是背一串原因,而是体现系统化定位能力。

可以按这个结构回答:

  1. 先分阶段:HTML、静态资源、JS 执行、应用挂载、接口数据、CSS 渲染。
  2. 再讲工具:Network 看资源,Console 看错误,Elements 看 DOM,Performance 看首屏时序,监控看影响面。
  3. 然后给典型场景:发版后 chunk 404、低版本浏览器语法不兼容、接口 pending 没有兜底。
  4. 最后讲治理:缓存策略、错误监控、白屏检测、降级 UI、发布回滚和资源保留。

一个比较完整的回答可以是:

我会先判断白屏发生在哪个阶段。如果主文档或 JS/CSS 没加载成功,就重点看 Network、CDN、缓存和发布记录;如果资源正常,就看 Console 是否有运行时错误或兼容性问题;如果脚本没报错,再检查应用根节点、框架挂载和路由守卫;最后看首屏接口是否 pending,以及 loading、empty、error 状态是否有兜底。线上治理上会接入错误监控、资源失败监控和白屏检测,并配合 HTML no-cache、静态资源保留历史版本来降低发版后白屏风险。

总结

白屏排查的关键是把“页面空白”拆成可验证的链路问题。

  • 先看资源是否加载成功,再看脚本是否执行成功。
  • 资源正常时,继续检查应用挂载、接口依赖和样式可见性。
  • 发版后白屏要重点关注 HTML 缓存、chunk 404、CDN 和发布策略。
  • 生产环境不能只靠用户反馈,需要接入错误监控、资源失败监控和白屏检测。
  • 好的治理目标不是保证永不出错,而是在出错时让页面有降级、有提示、可定位、可回滚。