跳到主要内容

首屏白屏问题排查

场景

用户反馈页面打开后长时间白屏才显示内容,或者某些用户一直白屏。你会怎么排查和解决?

分析思路

白屏的可能原因分类

第一步:快速定位是哪类问题

在白屏时打开 DevTools Console
// 1. 有红色报错 → JS 执行报错
// 2. Console 干净 → 资源加载问题或渲染阻塞

// 快速检查资源加载
const failedResources = performance.getEntriesByType('resource')
.filter((r) => (r as PerformanceResourceTiming).transferSize === 0);
console.log('疑似加载失败的资源:', failedResources);

第二步:针对不同原因的排查

原因 1:JS 执行报错导致白屏

SPA 应用中,如果根组件渲染报错,整个页面就会白屏。

✅ React ErrorBoundary 兜底
class ErrorBoundary extends React.Component<
{ children: React.ReactNode; fallback: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };

static getDerivedStateFromError() {
return { hasError: true };
}

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// 上报错误到监控平台
reportError({ error, errorInfo });
}

render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}

// 使用
<ErrorBoundary fallback={<ErrorPage />}>
<App />
</ErrorBoundary>
✅ 全局未捕获错误监听
// 捕获同步错误
window.addEventListener('error', (e) => {
reportError({
message: e.message,
filename: e.filename,
lineno: e.lineno,
colno: e.colno,
});
});

// 捕获 Promise 错误
window.addEventListener('unhandledrejection', (e) => {
reportError({
message: e.reason?.message || String(e.reason),
type: 'unhandledrejection',
});
});

原因 2:资源加载失败

✅ 脚本加载失败重试
function loadScriptWithRetry(src: string, maxRetries = 3): Promise<void> {
return new Promise((resolve, reject) => {
let retries = 0;

function tryLoad() {
const script = document.createElement('script');
script.src = retries === 0 ? src : src.replace('cdn1.com', 'cdn2.com'); // 切换 CDN
script.onload = () => resolve();
script.onerror = () => {
retries++;
if (retries < maxRetries) {
console.warn(`脚本加载失败,第 ${retries} 次重试: ${src}`);
tryLoad();
} else {
reject(new Error(`脚本加载失败: ${src}`));
}
};
document.head.appendChild(script);
}

tryLoad();
});
}
✅ HTML 中的 loading 状态
<!DOCTYPE html>
<html>
<body>
<div id="root">
<!-- JS 加载前显示的内容,JS 加载后会被 React 替换 -->
<div class="loading-skeleton">
<div class="skeleton-header"></div>
<div class="skeleton-content"></div>
</div>
</div>
<script src="app.js"></script>
</body>
</html>

原因 3:JS 包过大导致白屏时间长

诊断 JS 加载时间
// 查看 JS 资源加载耗时
const jsResources = performance.getEntriesByType('resource')
.filter((r) => r.name.endsWith('.js'))
.sort((a, b) => b.duration - a.duration);

jsResources.forEach((r) => {
console.log(`${r.name}: ${r.duration.toFixed(0)}ms, ${((r as any).transferSize / 1024).toFixed(0)}KB`);
});

优化方案:

方案效果
代码分割 + 懒加载减少首屏 JS 体积
SSR / SSG服务端直接返回 HTML
骨架屏感知优化,减少白屏感
Preload 关键资源提前加载关键 JS/CSS
Service Worker 缓存二次访问秒开
✅ 骨架屏方案
// 构建时自动生成骨架屏
// 或在 HTML 模板中手写

// React 中使用 Suspense
function App() {
return (
<Suspense fallback={<SkeletonPage />}>
<MainContent />
</Suspense>
);
}

function SkeletonPage() {
return (
<div className="skeleton">
<div className="skeleton-header animate-pulse" />
<div className="skeleton-body animate-pulse" />
</div>
);
}

原因 4:接口数据阻塞渲染

❌ 首屏依赖接口,接口慢则白屏
function HomePage() {
const [data, setData] = useState<HomeData | null>(null);

useEffect(() => {
fetchHomeData().then(setData); // 接口返回前,页面什么都没有
}, []);

if (!data) return null; // 白屏!
}
✅ 渐进式渲染 + 兜底
function HomePage() {
const { data, isLoading, error } = useQuery({
queryKey: ['home'],
queryFn: fetchHomeData,
staleTime: 5 * 60 * 1000, // 缓存 5 分钟
});

if (error) return <ErrorPage />;

return (
<div>
{/* 不依赖接口的部分先显示 */}
<Header />
<HeroSection />

{/* 依赖接口的部分单独处理 */}
{isLoading ? <ContentSkeleton /> : <MainContent data={data!} />}
</div>
);
}

原因 5:兼容性问题导致白屏

排查思路
// 1. 查看 Console 中的语法错误(Unexpected token 等)
// 2. 检查是否使用了目标浏览器不支持的 API

// ✅ 添加必要的 Polyfill
// vite.config.ts
import legacy from '@vitejs/plugin-legacy';

export default {
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
}),
],
};

白屏监控方案

白屏检测 SDK
function detectWhiteScreen(): boolean {
// 方案:采样检测页面关键位置是否有内容
const points = [
{ x: window.innerWidth / 2, y: window.innerHeight / 2 },
{ x: window.innerWidth / 4, y: window.innerHeight / 4 },
{ x: (window.innerWidth * 3) / 4, y: window.innerHeight / 2 },
];

const isWhite = points.every((point) => {
const el = document.elementFromPoint(point.x, point.y);
// 如果取到的是 html/body/根容器,说明没有实际内容
return !el || el === document.documentElement || el === document.body || el.id === 'root';
});

return isWhite;
}

// 在 page load 后 3 秒检测
setTimeout(() => {
if (detectWhiteScreen()) {
reportError({ type: 'white-screen', url: location.href });
}
}, 3000);

常见面试问题

Q1: SPA 应用白屏的常见原因有哪些?

答案

  1. JS 加载失败:CDN 挂了、网络差、CSP 策略拦截
  2. JS 执行报错:代码 bug、兼容性问题导致根组件渲染失败
  3. 资源体积过大:JS/CSS 过大导致加载和执行时间长
  4. 接口数据阻塞:首屏渲染依赖的接口没返回或报错
  5. CSP / CORS 错误:安全策略导致资源无法加载
  6. 缓存问题:旧 HTML 引用新 hash 文件名,404 报错

Q2: 如何实现白屏监控?

答案

两种常见方案:

  1. 采样点检测法:用 document.elementFromPoint() 检测页面关键位置是否有实际 DOM 节点
  2. MutationObserver:监听 DOM 变化,如果一定时间内根节点没有子元素变化,判定为白屏

触发时机:load 事件后延迟 3~5 秒检测,结合 FCP 指标辅助判断。

Q3: 如何减少 SPA 白屏时间?

答案

优化手段阶段效果
SSR / SSG服务端直接返回 HTML,FCP 极快
内联骨架屏HTML在 JS 加载前就有视觉内容
代码分割构建减少首屏 JS 体积
Preload 关键资源加载提前下载关键 JS/CSS
Service Worker缓存二次访问从缓存读取
ErrorBoundary运行时JS 报错时有兜底 UI
渐进式渲染渲染先渲染不依赖接口的部分

相关链接