白屏如何分析

面试回答总框架

线上页面白屏,我不会一上来就猜是 JS 报错,而是先把问题拆成三步:

  1. 定范围:全量还是部分用户;首屏白屏还是操作后白屏;单页面白屏还是全站白屏。
  2. 定阶段:页面从 URL 到可见内容,会经过 HTML、静态资源、JS 执行、框架挂载、接口请求、DOM/CSS 展示等阶段。白屏一定卡在某一层。
  3. 定证据:用监控、DevTools、错误日志、发布记录、灰度配置、用户环境互相印证,不只靠本地复现。

一句话回答:

我会按 HTML -> 静态资源 -> JS 执行 -> 框架挂载 -> 接口数据 -> DOM/CSS 展示 -> 灰度/缓存/兼容性 这条链路逐层排查;线上先止血,再定位根因,最后从发布、代码兜底和监控告警上治理。

先判断白屏是哪一种

面试时可以先主动把白屏分类,这会显得思路很稳。

现象优先怀疑
完全空白,连壳都没有HTML 没回来、网关/CDN/域名/证书异常、HTML 缓存错误
有导航、背景、Loading,但核心内容没有JS 报错、框架未挂载、首屏接口 pending、权限/路由卡住
先出现内容,随后变白异步状态覆盖、路由跳转异常、错误边界兜底不当、CSS 隐藏
只有部分用户白屏灰度、A/B 实验、浏览器兼容、Service Worker、缓存版本不一致、账号数据差异

记忆口诀:

先看有没有 HTML,
再看资源有没有,
再看 JS 跑没跑,
再看框架挂没挂,
再看接口卡不卡,
最后看 DOM 有没有被 CSS 藏起来。

Case 1:HTML 文档没有正常返回

现象

  • 页面完全空白。
  • Network 里 document 请求不是 200
  • 返回的是错误页、登录页、网关页,而不是业务 HTML。
  • 某些地区、运营商、CDN 节点访问异常。

怎么证明

打开 DevTools 的 Network,先看 document 类型请求:

  • 状态码是否是 200,有没有 404500502504
  • content-type 是否是 text/html
  • Response 内容是不是正常 HTML。
  • HTML 里引用的 JS/CSS 路径是否正确。
  • 是否命中了旧 HTML、错误 HTML、灰度 HTML。

如果 HTML 都没有正常返回,就不是 React/Vue 组件本身的问题,要往 CDN、Nginx、网关、服务端、DNS、证书方向查。

怎么处理

  • 查 CDN 边缘节点和回源状态。
  • 查 Nginx/网关访问日志。
  • 检查发布时是否改了 base、路由 fallback、静态目录。
  • 如果是全量问题,优先回滚或切回上一版 HTML。

面试追问

追问:你本地打开正常,但用户说白屏,怎么办?

自己能打开只能说明不是全量故障,不能证明线上没问题。我会拿到用户的 URL、时间、账号、地区、浏览器、设备、版本、是否灰度、监控 eventId,再按地域、UA、CDN 节点、release、用户 ID 聚合,看是不是某一类用户集中异常。

追问:HTML 返回了 200 就一定正常吗?

不一定。200 只能说明请求成功,还要看返回内容是不是业务 HTML。有些场景会把登录页、错误页、网关兜底页、旧 HTML 以 200 返回,前端看起来仍然会白屏。

Case 2:JS/CSS 静态资源加载失败

现象

  • HTML 正常返回,但入口 JS 没有执行。
  • Network 里 JS/CSS 出现 4045xx、MIME type 错误、CORS 错误、SRI 校验失败。
  • 页面只有一个空的 #root#app

现代 SPA 的 HTML 往往只有:

<div id="root"></div>
<script src="/assets/app.abc123.js"></script>

真正内容依赖 JS 渲染。如果入口 JS 加载失败,根节点永远不会挂载业务 DOM,用户看到的就是白屏。

怎么证明

重点看 Network:

  • JS/CSS 是否都是 200
  • JS 文件是否实际返回了 HTML 错误页。
  • content-type 是否正确,例如 JS 应该是 JavaScript MIME。
  • CDN 上资源是否存在。
  • HTML 引用的 hash 文件是否和部署产物一致。

常见原因

  • 用户拿到新 HTML,但 CDN 上新 JS 还没同步完成。
  • 用户拿到旧 HTML,但旧 JS 已经被清理。
  • 构建产物带 hash,但部署没有做到原子切换。
  • Nginx fallback 把 JS 请求转成了 HTML。
  • CDN CORS、crossorigin、SRI hash 配置错误。

怎么处理

  • 静态资源带 hash 后长期缓存,不覆盖、不提前删除。
  • HTML 使用短缓存或协商缓存。
  • 发布顺序改成先上传静态资源,再切换 HTML。
  • 保留上一版本资源一段时间。
  • 发布失败时能快速回滚 HTML 和静态资源。

面试追问

追问:如何区分资源加载失败和 JS 执行失败?

资源加载失败主要看 Network,入口 JS/CSS 请求会失败、返回错误内容或被浏览器拒绝执行;JS 执行失败通常资源是 200,但 Console 或错误监控里有运行时异常,堆栈指向入口、路由、状态初始化或组件。

追问:为什么发布后老用户白屏,新用户正常?

老用户可能命中了浏览器缓存、CDN 缓存或 Service Worker,拿到了旧 HTML/旧 runtime/旧 chunk 的混合版本;新用户首次访问拿到的是完整新版本。治理要靠 hash 资源长期保留、HTML 短缓存、资源先发再切 HTML、Service Worker 更新策略。

Case 3:JS 执行时报错

现象

  • 静态资源加载成功。
  • Console 或监控出现 JS runtime error。
  • 错误发生在首屏渲染之前。
  • 白屏用户和错误用户高度重合。

怎么证明

不要只说“看 Console”,更完整的证据链是:

  1. 错误发生时间早于首屏渲染时间。
  2. 错误堆栈指向入口文件、路由初始化、全局状态初始化、首屏组件。
  3. 错误 release 和白屏 release 一致。
  4. 错误用户集合和白屏用户集合高度重合。
  5. 本地切到同版本、同参数、同账号状态可以复现。

常见原因

  • 读取空对象:Cannot read properties of undefined
  • 低版本浏览器不支持新语法或新 API。
  • 第三方 SDK 初始化异常阻断主流程。
  • 路由懒加载 chunk 加载失败。
  • 环境变量缺失,入口初始化直接 throw
  • 本地缓存里的旧 chunk 与新 runtime 不匹配。

监控怎么做

至少要捕获两类错误:

window.addEventListener('error', (event) => {
  // JS 运行时错误、资源加载错误
});

window.addEventListener('unhandledrejection', (event) => {
  // 未处理的 Promise 异常
});

React 项目要有 Error Boundary,Vue 项目要配置 app.config.errorHandler

面试追问

追问:错误栈只有 app.js:1:12345 怎么定位?

要通过 release 版本找到对应 Source Map,在服务端还原堆栈。错误上报时必须带 release、构建 commit、文件名、行列号、用户环境,监控系统再用对应版本的 Source Map 把压缩位置还原到源码组件和行号。

追问:为什么第三方 SDK 也会导致白屏?

如果 SDK 初始化放在入口主流程里,而且失败后没有 try/catch 或降级,就可能阻断 render。第三方 SDK 应该异步加载、失败降级,不能成为 App 挂载的强依赖。

Case 4:框架没有挂载成功

现象

  • HTML 和资源都正常。
  • Console 没有明显 JS 报错。
  • #root#app 下没有业务 DOM。
  • 页面可能一直停在 Loading 或直接空白。

怎么证明

检查这些点:

  • #root#app 是否存在,入口选择器是否写错。
  • createRoot(...).render(...)app.mount(...) 是否执行。
  • 路由是否匹配到页面组件。
  • 权限守卫是否把用户重定向到空路由。
  • 根组件是否因为状态判断直接返回 null
  • 异步组件、懒加载组件是否一直没有 resolve。

典型例子

function App() {
  const { user, loading } = useAuth();

  if (loading) return null;
  if (!user) return <Login />;

  return <RouterProvider router={router} />;
}

如果 loading 因为接口异常一直是 true,页面就永久返回 null。这不是资源问题,也不一定有 JS 报错,但用户看到的是白屏。

更稳妥的写法是:

function App() {
  const { user, loading, error } = useAuth();

  if (loading) return <PageSkeleton />;
  if (error) return <PageError retry={reloadAuth} />;
  if (!user) return <Login />;

  return <RouterProvider router={router} />;
}

面试追问

追问:没有报错为什么也会白屏?

因为白屏不一定来自异常,也可能来自“合法地渲染了空”。比如根组件返回 null,路由没有匹配,权限状态一直初始化中,Suspense 没有合适 fallback,这些都可能没有错误栈,但页面没有内容。

追问:如何避免框架挂载层面的白屏?

入口要有兜底 UI;全局异步状态必须有 loading、error、timeout、retry;路由要有 404/兜底页面;懒加载失败要能重试;错误边界不能把整个页面兜成空白。

Case 5:首屏接口阻塞渲染

现象

  • 页面壳出现了,但核心内容一直不出来。
  • Network 里 XHR/Fetch pending 很久,或者出现 401403500、跨域失败。
  • 根组件或页面组件把接口成功作为渲染前置条件。

怎么证明

看 XHR/Fetch:

  • 是否 pending 时间过长。
  • 是否接口失败后没有进入 error 状态。
  • 是否一个接口失败阻塞后续所有请求。
  • 是否接口返回结构变化导致前端渲染异常。
  • 是否权限、用户信息、路由配置等关键接口没有兜底。

怎么处理

首屏接口要分级:

类型处理方式
关键数据有骨架屏、错误页、超时、重试;失败时不能永久空白
非关键数据局部 loading,失败后局部降级为空态
可延后数据首屏不可见区域延后请求或滚动到可见时再请求

面试中可以这样说:

我不会让所有接口都成为首屏前置条件。首屏只等待决定页面结构的关键数据,其他模块局部 loading、局部失败,不能把整个 App 渲染成空。

面试追问

追问:为什么接口失败会导致白屏?

因为代码可能把接口成功当成渲染前置条件。例如用户信息、权限、路由配置不返回时,根组件一直返回 null 或全局 Loading。解决不是假设接口永远成功,而是关键接口失败要有超时、错误页、重试和降级。

追问:接口 401 导致白屏怎么处理?

401 应该进入登录态处理或刷新 token 流程。如果刷新失败,要跳登录页或展示明确错误,不能让权限初始化一直 pending。权限链路尤其要避免“等待用户信息 -> 用户信息 401 -> 状态不落地 -> 永久 loading”。

Case 6:DOM 已经渲染,但被 CSS 或布局隐藏

现象

  • Elements 里能看到业务 DOM。
  • 页面肉眼仍然是白色或只有空区域。
  • 关闭某些样式后内容出现。

常见原因

  • 根节点高度为 0
  • 内容颜色和背景色相同。
  • 全屏 Loading、蒙层、空 div 覆盖内容。
  • z-index 层级错误。
  • opacity: 0visibility: hiddendisplay: none 没被恢复。
  • 移动端安全区、视口单位、兼容样式导致内容被挤出屏幕。

怎么证明

在 Elements 面板里看:

  • #root 下是否已经有业务 DOM。
  • 选中首屏元素,看 computed style。
  • 临时关闭可疑样式,看内容是否出现。
  • 执行 document.body.innerText 判断是否有文本内容。
  • 截图或采样点判断首屏是否全是背景色。

核心判断:

有 DOM 但不可见,是 CSS/布局问题;没有 DOM,继续查 JS、框架、接口链路。

面试追问

追问:如果 FCP 有,但用户仍说白屏,说明什么?

说明浏览器确实绘制过内容或背景,但首屏主内容可能没出现。可能是 CSS 隐藏、全屏 Loading、主接口慢、LCP 元素迟迟不出现,所以要结合 LCP、关键元素检测、截图和 DOM 状态判断。

Case 7:只有部分用户白屏

现象

  • 某些浏览器、WebView、地区、账号、灰度组出现。
  • 新用户正常,老用户异常。
  • 线上错误率不是全量升高,而是集中在某些维度。

排查维度

维度可能原因
release某个版本引入问题
灰度/A/B某个实验配置或远程开关异常
浏览器/系统新语法、新 API、polyfill 缺失
地域/CDN 节点节点缓存、回源、同步异常
用户/账号权限、数据结构、账号状态差异
Service Worker新旧 HTML/JS/chunk 混用

兼容性怎么查

典型表现:

  • Chrome 正常,低版本 Safari/Android WebView 白屏。
  • Console 报 Unexpected token
  • Console 报 Object.fromEntries is not a functionResizeObserver is not defined

治理方式:

  • 检查 Browserslist、Babel/SWC 转译目标。
  • 检查 polyfill 是否注入。
  • 检查第三方依赖是否包含未转译的新语法。
  • 用真实机型或浏览器云复现。

Service Worker 怎么查

Service Worker 可能让用户拿到“旧 HTML + 新 JS”或“新 HTML + 旧 JS”。

治理方式:

  • HTML 使用 network first。
  • hash 静态资源使用 cache first。
  • SW 更新后提示用户刷新。
  • 发布异常时可以远程关闭或跳过 SW。
  • 缓存策略区分 HTML、静态资源、接口。

面试追问

追问:灰度问题怎么处理?

先关灰度或实验开关止血,再定位代码。灰度的价值就是把影响面限制住,不能让用户等我们慢慢排查。定位时对比实验配置、远程字段、新组件加载路径和白屏用户集合。

追问:如何证明是兼容性问题?

按 UA 和系统版本聚合错误率。如果白屏集中在低版本 Safari、Android WebView 或某个厂商浏览器,同时错误是语法/API 不支持,再用对应真机复现,就能形成证据链。

Case 8:线上监控如何发现白屏

白屏不能只靠用户反馈,应该主动监控。

白屏检测

基础版本可以检查根节点是否有内容:

function checkWhiteScreen() {
  const root = document.querySelector('#root');
  const hasContent = Boolean(root?.children.length);

  if (!hasContent) {
    report({
      type: 'white_screen',
      url: location.href,
      userAgent: navigator.userAgent,
      release: window.__APP_VERSION__,
      time: Date.now(),
    });
  }
}

setTimeout(checkWhiteScreen, 3000);

但只判断 #root.children.length === 0 不够,它只能发现“根节点没挂载”。更可靠的方案要结合:

  • 根节点子元素数量。
  • 首屏关键元素是否出现。
  • 页面截图或采样点是否都是背景色。
  • FP、FCP、LCP 是否异常。
  • JS error、resource error、API error 是否同时发生。

资源错误监控

window.addEventListener(
  'error',
  (event) => {
    const target = event.target as HTMLElement;

    if (target && ['SCRIPT', 'LINK', 'IMG'].includes(target.tagName)) {
      report({
        type: 'resource_error',
        tagName: target.tagName,
        url:
          (target as HTMLScriptElement).src ||
          (target as HTMLLinkElement).href,
      });
    }
  },
  true,
);

第三个参数要用 true,因为资源加载错误通常不会冒泡,需要在捕获阶段监听。

指标怎么解释

指标说明
TTFB 高偏服务端、网关或网络问题
资源错误率高偏 CDN、部署、缓存、资源路径问题
FCP 没有页面基本没有内容绘制
FCP 有但 LCP 很晚主内容、图片、接口或布局阻塞
JS error rate 高偏运行时错误或兼容性问题
API success rate 低偏接口、权限、跨域、服务端异常

面试追问

追问:白屏检测只看根节点够吗?

不够。有些页面有 DOM,但被 CSS 隐藏;有些页面一直显示全屏 Loading;有些页面只渲染了壳,核心内容没出来。所以要结合关键元素、采样点、性能指标、资源错误、JS 错误、接口错误一起判断。

Case 9:线上如何止血

白屏是高优问题,排查和止血要并行。

全量白屏

优先级:

  1. 回滚最近发布。
  2. 切回上一版 HTML。
  3. 恢复上一版静态资源。
  4. 关闭高风险远程配置。
  5. 通知服务端、网关、运维一起看核心链路。

全量白屏不要边猜边修,先恢复用户可用性。

部分白屏

优先级:

  1. 关闭对应灰度、实验、远程开关。
  2. 降级问题模块。
  3. 对特定浏览器或 WebView 走兼容降级。
  4. 保留现场数据:版本、用户、UA、错误栈、接口状态、资源状态。

新版本白屏

先回滚或暂停发布,然后按 release 定位:

  • 新增依赖是否引入兼容问题。
  • 构建配置是否变化。
  • 路由表、权限、接口字段是否变化。
  • 静态资源上传和 HTML 切换顺序是否变化。

面试追问

追问:全量白屏先排查还是先回滚?

先止血。全量白屏影响核心路径,优先回滚、切旧版本、关开关,同时保留日志和现场。恢复后再复盘根因。只有回滚风险更大或无法回滚时,才考虑热修。

最终背诵模板

面试时可以直接按这个版本回答:

我会先定范围:是全量还是部分用户,是首屏白屏还是操作后白屏,是单页面还是全站。

然后按加载链路排查:
1. 看 document 请求,确认 HTML 是否正常返回,是否返回了错误页、旧 HTML 或网关页。
2. 看 JS/CSS 静态资源是否加载成功,有没有 404、MIME、CORS、SRI、CDN 缓存问题。
3. 看 Console 和错误监控,确认 JS 是否在入口、路由、状态初始化、首屏组件报错。
4. 看框架是否挂载,root 下有没有 DOM,路由、权限、Suspense、异步组件是否返回了空。
5. 看首屏接口是否 pending 或失败,是否把关键接口当成全局渲染前置条件。
6. 如果 DOM 已经存在但不可见,再查 CSS、蒙层、z-index、display、opacity、根节点高度。
7. 如果只有部分用户出现,按 release、灰度、浏览器、地域、账号、Service Worker 和缓存继续缩小范围。

线上处理上,全量白屏先回滚或关开关止血;部分白屏先关灰度或降级模块。事后从发布原子性、资源缓存策略、错误边界、异步兜底、白屏检测和监控告警上治理。

一句话总结

白屏排查的核心不是猜原因,而是按页面加载链路逐层证明;线上先止血,定位靠证据,复盘要落到发布机制、兜底渲染和监控告警。