在 SSG 页面中,如果构建阶段固化的是 PC 布局,移动端用户首次打开页面时,往往会先看到 PC 样式,再在客户端 JavaScript 执行后切换为移动端布局。这类闪屏不仅影响观感,还可能带来 CLS、LCP 和 SEO 风险。
本文从渲染链路出发分析问题根因,对比六种治理方案,并给出兼顾开发成本、用户体验、SEO 与长期维护性的选择建议。
问题根因
当前 SSG 页面的渲染链路可以概括为:
- 浏览器首先渲染构建阶段生成的 PC 静态 HTML。
- 客户端 JavaScript 下载并执行。
- React 应用重新渲染,并直接替换静态页面的根节点。
- 依赖视口的组件重新计算响应式类名、行内样式和布局分支。
因此,在移动端和平板端,第一次绘制使用的是构建阶段固化的 PC 样式和布局状态。只有 React 应用启动并完成视口相关计算后,页面才切换为正确布局。
响应式容器是一个典型例子,它会根据屏幕宽度生成不同的类名:
larkwebsite-pc-responsive
larkwebsite-tablet-fixed-responsive
larkwebsite-tablet-elastic-responsive
larkwebsite-mobile-responsive
业务样式再依赖这些类名:
.larkwebsite-pc-responsive {
// PC 样式
}
.larkwebsite-mobile-responsive {
// Mobile 样式
}
除此之外,Header 等组件内部也可能通过 JavaScript 动态计算样式。这些逻辑不一定表现为响应式类名切换,也可能直接生成行内样式,或者根据视口选择不同的布局结构。构建阶段使用 PC 环境执行时,这些组件同样会把 PC 状态固化到静态 HTML 中。
两个核心目标
治理这个问题时,需要同时满足两个目标。
SEO 正确
爬虫能够获取完整、可索引且与用户最终内容一致的 HTML,包括标题、正文、链接和结构化数据。
用户首屏正确
用户首次可见内容就符合真实视口,不先展示 PC 布局再切换,并尽量避免闪屏和 CLS。
方案一:增加全屏 Loading
在静态 HTML 中预置覆盖首屏的 Loading 或响应式 Skeleton,确保它通过独立的内联 CSS 在首次绘制时立即生效。React 应用完成初始化、根据真实视口生成正确布局并提交首帧后,再移除遮罩。同时设置超时降级,避免脚本加载失败时永久遮挡正文。
优点
- 工作量和改动成本较低,主要修改页面启动链路。
- 对业务影响较小,不需要立即改造响应式容器、Header 等依赖 JavaScript 计算样式的组件,适合快速覆盖存量页面。
- 正文仍保留在 HTML 或活动 DOM 中,SEO 直接索引风险较低。
- 使用接近最终结构的响应式 Skeleton,可以减少错误布局暴露和切换时的视觉跳变。
缺点
- 只隐藏错误布局,没有解决首屏样式依赖 JavaScript 计算的根因。
- 全屏转圈或纯色遮罩可能把闪屏变成白屏等待,恶化 LCP 和主观加载速度。
- 业务侧仍需统一 Loading 的关闭时机,长期还需迁移到真正的响应式治理方案。
方案二:首屏布局改用 CSS Media Query
将首屏可见区域以及 Header 中依赖 JavaScript 计算的响应式类名、行内样式和布局分支迁移到 CSS Media Query。
静态 HTML 保持稳定结构,浏览器在样式计算阶段直接根据真实视口选择布局:
/* 改造前 */
.responsive-mobile .hero {
padding: 40px 20px;
}
/* 改造后 */
@media (max-width: 599.99px) {
.hero {
padding: 40px 20px;
}
}
优点
- 长期维护成本较低,基础布局由 CSS 负责,职责边界清晰。
- 首次绘制即可得到正确布局,不依赖 React 启动,也不需要额外遮罩或前置修正脚本。
- 对新增业务影响可控,形成统一规范后可以避免继续产生同类问题。
- SEO 影响最小,正文和 DOM 结构稳定,移动端可用性、LCP 与 CLS 更容易控制。
缺点
- 存量改造工作量高,跨团队推进和回归测试成本较大。
- 业务侵入性强,需要逐一改造响应式容器使用方,以及 Blog Header 等内部动态计算样式或结构的组件。
方案三:保存多端 HTML,客户端或服务端选端
构建或保存 Schema 时,按照站点自身的响应式规范生成 PC、Mobile 等多份静态 HTML。运行时可以采用客户端选端或服务端选端。
客户端选端
使用 <template> 保存未激活版本,并在首次绘制前通过 matchMedia 实例化当前视口对应的 DOM:
<style>
#app {
visibility: hidden;
}
</style>
<body>
<template data-screen="mobile">...</template>
<template data-screen="tablet">...</template>
<div id="app">
<!-- 默认 PC DOM -->
</div>
<script>
replaceResponsiveHtml();
document.getElementById('app').style.visibility = 'visible';
</script>
<!-- React 和其他脚本 -->
</body>
服务端选端
由 CDN 或应用服务根据请求 UA 和 Client Hints 判断设备类型,只返回对应端 HTML,从源头减少 HTML 传输与解析体积。实现时需要注意:
- 优先读取
Sec-CH-UA-Mobile等 Client Hints,并以 User-Agent 作为兼容判断。 - 将
deviceType纳入 CDN 缓存 Key,并正确设置Vary,避免 PC 与 Mobile HTML 串缓存。 - 保证各端标题、正文、链接和结构化数据语义一致,降低 SEO 内容差异风险。
- 客户端启动后使用
matchMedia复核服务端判断,在不一致时轻量纠偏或请求正确端页面。 - 端类型、断点和构建视口需要支持按站点配置,Tablet 的归属也应由站点决定。
优点
- 初期工作量和改动成本中等,主要集中在构建链路、客户端启动逻辑或服务端/CDN 选端与缓存逻辑。
- 对业务代码侵入较小,不需要逐一重构组件内部依赖 JavaScript 的响应式实现。
- 每个视口都能获得构建时已计算完成的静态结构,首屏结果确定。
- 客户端使用
<template>可避免重复 ID、隐藏资源提前加载和脚本副作用。 - 服务端只下发命中端 HTML,可显著降低 HTML 传输和解析体积。
缺点
- 客户端选端仍会使 HTML 传输和解析体积随模板数量增长,构建和多份产物验证成本也会增加。
- 服务端选端依赖 UA 和 Client Hints 的判断准确性,并要求按
deviceType隔离缓存。 <template>内容不是活动 DOM,不能假设源码中存在正文就一定会被索引。- 前置脚本失败、执行过晚或各端内容不一致,可能造成内容不可见、重复内容或结构化数据差异,需要持续进行 SEO 渲染验证。
方案四:爬虫使用 SSG,普通用户使用 CSR
由 CDN 或服务端识别请求是否来自搜索引擎爬虫:爬虫返回包含完整正文的 SSG HTML,普通用户返回 CSR Shell,再由客户端 React 根据真实视口渲染页面。识别逻辑应优先使用 CDN 或 WAF 的 Bot Detection 结果,并将响应类型纳入缓存 Key。
优点
- 改动主要集中在 CDN、服务端、缓存和部署链路。
- 对现有业务组件侵入较小,不需要大规模修改组件内部的响应式代码。
- 普通用户不会看到构建阶段固化的 PC 页面,也不需要维护多端 HTML。
- 在识别准确且内容一致的前提下,爬虫可直接获得完整静态正文。
缺点
- Bot 识别、缓存隔离和部署策略需要持续维护,运维复杂度较高。
- User-Agent 无法覆盖所有搜索引擎、分享机器人、SEO 工具和 AI 爬虫。
- 普通用户失去 SSG 的首屏优势,弱网环境中的 LCP 表现可能变差。
- SEO 风险最高:爬虫与用户内容不一致可能被判定为 cloaking,识别错误或缓存污染还可能让爬虫拿到空 Shell。
方案五:在 Body 解析前安装响应式类名校正器
在 <head> 中同步安装前置脚本,根据 matchMedia 计算当前视口对应的响应式类型,并通过 MutationObserver 监控后续解析或 React 新增的 DOM。节点出现构建阶段生成的 PC 响应式类名时,脚本在首次绘制前尽快替换为当前端类名。断点和边界规则必须与 React ResponsiveContainer 保持一致。
优点
- 对于主要通过统一响应式类名控制布局的页面,初期工作量可控,接入集中在全局启动脚本。
- 无需立即重写全部 CSS,能够同时处理 SSG DOM 和后续 React DOM,较快改善非 PC 端闪屏。
- 只修改 class 时不改变正文和语义结构,SEO 直接影响较小。
缺点
- 总体工作量和改动成本可能达到中高水平,并产生持续维护成本。
- Header 等动态生成行内样式、尺寸、位置或渲染分支的组件仍需逐一适配。
- 长期容易出现校正器与业务断点规则漂移,并增加回归测试、
MutationObserver性能和 CSP 处理成本。
方案六:CSS Media Query + 多端 HTML 混合治理
按照问题类型组合使用方案二和方案三,而不是在两者之间二选一。
宽度、间距、字号、排列、显示隐藏等基础样式差异统一迁移到 CSS Media Query,保证 SSG 和 React 输出稳定 DOM,并在首次绘制时由浏览器直接选择正确样式。只有当 PC、Tablet 和 Mobile 的组件树、节点顺序或功能组合存在本质差异,无法通过合理的共享 DOM 和 CSS 表达时,才针对这些页面或局部模块生成多端 HTML,并在首次绘制前选择对应版本。
优点
- 按问题类型使用合适技术,既解决基础样式依赖 JavaScript 的根因,又覆盖无法统一 DOM 的结构差异。
- 只复制真正存在结构差异的模块,可降低 HTML 体积、构建耗时和多份产物维护成本。
- 业务影响可以分层治理:普通布局组件遵循 CSS 规范,少数复杂组件才接入多端构建。
- 大部分正文处于稳定活动 DOM 中,局部多端内容保持语义一致时 SEO 风险较低。
- 用户首次绘制能够同时获得正确样式和正确结构。
缺点
- 前期工作量较高,需要建立样式差异与结构差异的判定标准。
- 需要同时建设 CSS 响应式规范、多端构建、站点断点配置和模板选择机制。
- 存量页面仍需分类迁移,错误选型可能造成过度拆分或复杂 DOM。
- 局部多端 HTML 仍会增加体积和一致性验证成本,并需提供真实 DOM 降级。
- 团队需要长期维护统一断点、组件规范和测试矩阵。
六种方案对比
| 方案 | 工作量 | 业务影响 | 用户体验 | SEO 影响 |
|---|---|---|---|---|
| 全屏 Loading / Skeleton | 低 | 低 | 可隐藏错误布局,但可能变成白屏等待并恶化 LCP | 低到中,取决于正文是否保留在活动 DOM |
| CSS Media Query | 高 | 高 | 最佳,无额外遮罩和修正脚本 | 风险最低,HTML 和 DOM 稳定 |
| 多端 HTML + 客户端/服务端选端 | 中 | 低到中 | 较好,服务端命中正确时无闪屏 | 中,需要保证各端语义一致并避免串缓存 |
| 爬虫 SSG、用户 CSR | 中高 | 低到中 | 较差,普通用户失去 SSG 首屏优势 | 风险最高,存在误识别和 cloaking 风险 |
| 前置类名校正器 | 中高 | 中高 | 一般到较好,不能保证覆盖所有动态样式 | 低,但脚本异常和 CLS 有间接影响 |
| CSS + 局部多端 HTML | 高 | 中高 | 最佳,首次绘制同时获得正确样式和结构 | 低,局部多端内容需保持语义一致 |
推荐决策
综合开发成本、用户体验、SEO 和长期维护性,建议按以下顺序推进:
- 将方案六作为最终治理方向:基础样式统一使用 CSS Media Query,只有无法统一 DOM 的结构差异才使用多端 HTML。
- 近期 Blog 等重点页面先按方案二改造,优先消除首屏布局对 JavaScript 的依赖。
- 需要一次兼容大量旧页面时,可先采用方案三,但应限制多端 HTML 的范围,避免所有模块无差别复制。
- 新增页面默认遵循方案二,并在组件设计阶段判断是否存在必须使用多端结构的场景。
- 方案一只能作为短期视觉兜底;方案四和方案五不建议作为长期架构。
最终原则很明确:能用稳定 DOM 和 CSS 解决的响应式问题,不要交给客户端 JavaScript;只有真正的结构差异,才值得引入多端 HTML。这样才能同时保证首屏正确、SEO 稳定和架构可持续演进。