在静态站点生成(SSG)场景中,构建阶段无法获取用户的运行时状态,例如登录态、AB 实验分组等。如果为了这些状态改用整页客户端渲染,会引入首屏闪烁、布局偏移和 Hydration 不一致等问题。
对于只影响页面展示、且允许多个版本同时存在于静态 HTML 中的场景,可以在页面首次绘制前读取运行时变量,并把状态写入 <html> 根节点的 data-* 属性,再由 CSS 控制对应 DOM 的显隐。
实现思路
整体流程如下:
- SSG 产物同时输出登录态和未登录态的展示节点。
- 服务端或页面容器向
window.__globalVars__注入运行时信息。 - 在
<head>中同步执行一段轻量脚本。 - 脚本读取
loginStatus,在<html>上写入data-login-state。 - 首屏 CSS 根据该属性隐藏不匹配的 DOM。
最终 HTML 根节点会呈现为以下两种状态之一:
<html data-login-state="logged-in">
<html data-login-state="logged-out">
相比添加 is-logged-in、is-logged-out 两个 class,单个 data-login-state 属性更适合表达互斥的业务状态,也能避免两个冲突 class 同时出现。
Head 脚本
脚本必须放在 window.__globalVars__ 完成注入之后,并尽量位于首屏样式和页面内容渲染之前,也就是放在Body之前,就可以在页面渲染之前实现属性的注入,之后页面渲染时,就会按照指定的样式分支取渲染:
<script>
(() => {
let globalVars = window.__globalVars__;
// 兼容注入结果是 JSON 字符串的情况
if (typeof globalVars === 'string') {
try {
globalVars = JSON.parse(globalVars);
} catch {
globalVars = {};
}
}
const isLoggedIn = Boolean(globalVars?.loginStatus);
document.documentElement.setAttribute(
'data-login-state',
isLoggedIn ? 'logged-in' : 'logged-out'
);
})();
</script>
CSS 显隐规则
页面节点使用语义明确的 class:
<section class="logged-in-content">
登录用户看到的内容
</section>
<section class="logged-out-content">
未登录用户看到的内容
</section>
对应 CSS:
html[data-login-state='logged-in'] .logged-out-content,
html[data-login-state='logged-out'] .logged-in-content {
display: none;
}
为了避免运行时变量异常或脚本尚未执行时同时展示两份内容,可以增加保守的默认规则:
html:not([data-login-state]) .logged-in-content {
display: none;
}
这样脚本异常时页面默认按未登录态展示。
在 React SSG 中使用
组件本身不再读取 window,而是稳定地输出两份静态结构:
export function HomePage() {
return (
<>
<div className="logged-in-content">
<LoggedInHome />
</div>
<div className="logged-out-content">
<PublicHome />
</div>
</>
);
}
服务端生成的 HTML 和 React Hydration 看到的 DOM 结构保持一致,状态只影响 CSS,不需要在首次渲染时调用 setState,因此不会产生 Hydration mismatch。
扩展到 AB 实验
同样的模式也可以表达 AB 实验分组:
document.documentElement.dataset.abVariant = 'aiV2';
[data-ab-content] {
display: none;
}
html[data-ab-variant='control'] [data-ab-content='control'],
html[data-ab-variant='aiV2'] [data-ab-content='aiV2'] {
display: block;
}
根节点可以同时保存多个互不干扰的运行时状态:
<html data-login-state="logged-in" data-ab-variant="aiV2">
适用范围
该方案适合:
- 登录前后文案、按钮和普通模块的切换。
- 小范围 AB 文案或样式实验。
- SSG 页面中需要在首次绘制前确定的展示状态。
- 不包含敏感数据的纯展示内容。
不建议用于:
- 登录后才有权限访问的敏感数据。
- 两个版本都包含大量图片、视频或复杂交互组件的整页实验。
- 需要保证隐藏版本完全不下载资源的场景。
CSS 隐藏不等于权限隔离。隐藏节点仍然存在于 HTML 和 DOM 中,用户可以通过开发者工具查看。敏感信息必须由接口鉴权,并且只在鉴权成功后请求和渲染。
另外,即使图片节点被 display: none 隐藏,浏览器仍可能下载其资源。对于较重的隐藏模块,应在运行时只挂载命中的版本,或者采用多份静态产物配合 Edge 重写。
结论
对于 SSG 页面中的轻量登录态展示切换,将状态同步写入 <html> 的 data-* 属性,再由首屏 CSS 控制 DOM 显隐,是一种实现简单、不会破坏 Hydration、且基本无首屏闪烁的方案。
它解决的是展示分流,不是身份认证或权限控制。只要明确这个边界,就可以用于登录态 UI 和小型 AB 实验。