什么是首屏加载?
从技术定义上讲,首屏加载是指用户从输入 URL 回车开始,到浏览器渲染出屏幕可见区域内完整内容的过程。
通常关注这几个核心指标(Web Vitals):
- FP (First Paint): 首次渲染,即屏幕从白屏变成有东西(哪怕只是个背景色)。
- FCP (First Contentful Paint): 首次内容渲染,看到了文字或图片。
- LCP (Largest Contentful Paint): 最大内容渲染,这是最关键的指标,标志着首屏的核心内容(如 Banner 图或标题)已完成。
决定首屏加载的因素有哪些?
网络与传输效率(通道有多宽?)
这是最基础的一层,决定了数据资源多快能到达用户的设备。
- TTFB(首字节响应时间): 服务器处理请求的速度。如果后端接口慢、数据库查询未索引,或者服务器物理距离太远,前端再怎么优化也是徒劳。
- CDN 分发: 静态资源(JS, CSS, 图片)是否部署在离用户最近的边缘节点。
- HTTP 协议版本: * HTTP/1.1 存在队头阻塞,且有 6 个并发连接限制。HTTP/2 引入了多路复用,极大提升了多小文件的并发传输效率。
- 带宽与资源体积: 资源被压缩得够不够(Gzip/Brotli)?图片是否经过了无损压缩?
资源加载策略(谁先走,谁后走?)
浏览器在解析 HTML 时是有顺序的,错误的顺序会导致严重的渲染阻塞。
CSS 阻塞: 浏览器必须先下载并解析完 CSS 才能构建渲染树(Render Tree)。如果 CSS 文件巨大,白屏时间会显著增长
JS 阻塞: 默认情况下,浏览器遇到 <script> 标签会停止 HTML 解析,等待 JS 下载并执行。
- 预加载技术: 是否使用了 preload(提前加载关键资源)或 prefetch(空闲加载未来资源)。
渲染架构选择(在哪儿渲染?)
- CSR(客户端渲染): 传统的 Vue/React SPA。缺点: 必须等 JS 下载、解析、执行后,再去请求数据,最后才生成 HTML。这往往是“白屏”的重灾区。
- SSR(服务端渲染): 如 Nuxt.js 或 Next.js。优点: 服务器直接返回完整的 HTML 字符串,浏览器拿到即刻渲染,LCP 时间大幅提前。
- SSG(静态站点生成): 提前把页面编译成静态 HTML
浏览器执行性能(设备跑得快吗?)
当资源到达浏览器后,剩下的就是 CPU 和内存的活了。
- JavaScript 执行开销: 现代前端项目往往有庞大的 vendor.js。如果 JS 逻辑太重,即使下载完了,浏览器在解析(Parse)和执行(Compile)时也会卡住主线程。
- DOM 结构的复杂度: 过于嵌套的 HTML 结构会增加样式计算和布局(Layout)的耗时。
- 数据请求时机: 接口是并行请求还是串行瀑布流?首屏所需的 API 是否在 HTML 加载时就尽早发起了?
白屏问题排查方向
网络与资源加载层(资源到了吗?)
这是最直观的一步,打开 Chrome DevTools 的 Network 面板。
- 检查状态码: 是否存在 404 (资源找不到了) 或 5xx (服务器挂了) 的关键 JS/CSS 文件。
- 检查 DNS 与 证书: 域名解析是否失败?HTTPS 证书是否过期?(会导致资源被浏览器拦截)。
- 资源阻塞: 是否有一个超大的图片或三方脚本加载过慢,导致后续解析被挂起
运行时错误层(代码跑通了吗?)
如果 Network 面板显示资源全部 200 OK,但屏幕依然雪白,看 Console 面板。
- JS 运行时报错: 最常见的白屏原因。例如:Uncaught TypeError: Cannot read property 'map' of undefined。如果这个错误发生在 Vue/React 的初始化阶段,整个应用挂载就会中断。
- 资源加载顺序错误: 比如插件 A 依赖库 B,但 A 比 B 先执行了。
- 缓存问题: 强制刷新 (Ctrl+F5) 看看。有时候旧版的 Service Worker 或浏览器缓存了有 Bug 的代码
- 资源路径错误: 在 SPA 中,如果配置了 publicPath 错误,打包后的脚本路径指向了不存在的目录
架构与环境配置层(配置对了吗?)
- 浏览器兼容性: 你是否使用了某些老旧浏览器不支持的 ES6+ 语法(如可选链 ?.),且 Babel 没转译到位?这在低版本安卓或 iOS 上经常导致白屏且无明显报错。
- 路由配置: 是否因为路由重定向到了一个不存在的页面,且没有配置 404 兜底,导致页面渲染了“空气”。
- API 接口阻塞: 有些架构在渲染首屏前会 await 一个关键接口(如用户信息、权限)。如果这个接口一直 Pending 或超时,页面就会卡在初始状态。
首屏加载速度优化‼️
资源体积“减负”(核心:减少下载量)
这是最直接的优化方式,文件越小,传输越快。
- 路由懒加载 (Code Splitting): 将单页应用拆分成多个 JS 包,只加载当前页面需要的代码。
- Tree Shaking: 确保构建工具(Webpack/Vite)剔除掉库中未使用的代码(如只引用了 lodash 的一个函数,不要打包全量库)。
- 依赖按需引入: 针对 Element Plus、Ant Design 等组件库,配置插件实现按需打包。
- 图片压缩与格式优化:使用 WebP/Avif 格式(体积通常比传统 JPEG 小 30%-50%)。响应式图片: 使用 srcset 根据屏幕分辨率加载不同尺寸的图片,避免手机端下载 4K 大图。
传输链路“加速”
让资源以最快的路径到达客户端。
- 开启 HTTP/2 / HTTP/3: 利用多路复用减少连接开销,解决 HTTP/1.1 的队头阻塞。
- 部署 CDN: 将静态资源分发到边缘节点,减少物理距离带来的网络延迟。
- Gzip / Brotli 压缩: 对文本资源(JS/CSS/HTML)开启服务端压缩。
- 强缓存策略: 为静态资源文件名添加 Hash,并设置长期的 Cache-Control,让二次访问实现“秒开”。
关键路径“抢跑”
让浏览器先处理那些能让页面“看起来”完整的内容。
- 优化关键渲染路径 (CRP):内联关键 CSS (Critical CSS): 将首屏所需的 CSS 直接写在 <style> 标签里,避免外链 CSS 下载阻塞渲染。异步加载 JS: 使用 defer 或 async,防止脚本阻塞 HTML 解析。
- 资源预加载 (Resource Hints):<link rel="preload">:用于提前加载首屏的大图(Banner)或核心字体。<link rel="preconnect">:提前完成第三方 API 域名的 DNS 解析和建立连接。
- 接口抢跑: 别等框架(Vue/React)挂载完才请求数据。在 HTML 头部嵌入一段小脚本,或者在路由跳转时同步发起首屏 API 请求。