创见博客
低代码页面的 SSG 快照与 React 运行时接管机制
七崽爱吃小饼干2026/07/24阅读 2

在低代码平台生成页面时,一个核心问题是:如何既保证首屏能快速展示,又保留运行时的交互能力?

我们当前的页面发布链路采用的是一种「SSG 静态快照 + React 运行时重渲染」的模式。页面在浏览器中会先展示构建期生成的静态 HTML,然后再加载运行时脚本,由 React 接管 #app 节点。

这套机制看起来像 SSR/SSG + Hydration,但严格来说并不是标准 hydration。因为运行时代码使用的是 ReactDOM.render,不是 ReactDOM.hydrate。

整体链路

页面从低代码平台到线上访问,大致经过以下流程:

text
研发开发 template,并预留 slot
  ↓
运营在 CMS 中选择 template 创建页面
  ↓
运营填写页面 data
  ↓
低代码平台 preview 渲染页面内容
  ↓
手动触发 SSG 缓存
  ↓
将 preview 产物保存到 schema.data.ssg
  ↓
打包时读取 schema.data.ssg 构建初始 HTML
  ↓
浏览器加载 HTML,先展示静态页面
  ↓
执行底部 script
  ↓
ReactDOM.render 到 #app
  ↓
React 基于 window._templateValue 重渲染并接管页面

这个链路中有两个非常关键的数据来源:

text
schema.data.ssg

用于生成首屏静态 HTML。

text
window._templateValue

用于 React 运行时重新渲染页面。

可以简单理解为:

text
data.ssg 是构建期静态快照
_templateValue 是运行时渲染数据

Template 开发、CMS 填数与 SSG 缓存

页面并不是由运营直接编辑低代码组件结构生成的。更准确的分工是:研发负责开发 template,在 template 中定义页面结构、组件组合和可配置的 slot;运营负责在 CMS 中基于某个 template 创建页面,并填写具体业务数据。

template 里会预留 slot,例如标题、封面图、正文、标签、作者、阅读时长等。运营在 CMS 中填写的 data 会进入这些 slot,最终形成该页面的 schema 数据。

因此,一个页面通常由两部分共同决定:

text
template:研发维护的页面结构和组件逻辑
data:运营在 CMS 中填写的业务内容

当运营完成 data 填写后,可以在平台上通过 preview 获取页面渲染结果。preview 渲染出来的是一份完整 HTML,它代表当前 data 在当前 template 下的页面表现。

之后手动触发 SSG 缓存,平台会将这份 preview 产物保存到 schema 上,例如:

js
schema.data.ssg

这一步的作用是把一次动态渲染结果固化下来,作为后续打包生成初始 HTML 的输入。

因此,在当前流程下,运营修改 CMS data 后,需要重新保存 SSG,并重新打包发布页面,才能让线上 HTML 更新。

构建阶段如何生成 HTML

打包时,构建流程会读取低代码 schema。

其中 data.ssg 会被直接用于生成页面的初始 HTML。所以最终产物里,#app 不是一个空容器,而是已经包含完整页面内容。

示意结构如下:

html
<body>
  <div id="app">
    <div data-page-id="Root">
      <h1>Project Management Dashboard: Your Complete Guide with Best Templates</h1>
      <div class="editor-kit-container">
        <!-- 文章正文 -->
      </div>
    </div>
  </div>
</body>

浏览器拿到 HTML 后,不需要等待 React 加载完成,就可以先展示页面内容。

与此同时,构建产物中还会注入运行时数据:

js
window._templateValue = {
  title: 'Project Management Dashboard: Your Complete Guide with Best Templates',
  coverImage: '...',
  content: {...},
  blogTagList: ['comparisons', 'salesAndCrm'],
  author: 'Donna Shao',
  readDuration: '20'
}

这些数据会在 React 运行时接管页面时使用。

浏览器中的首屏展示

浏览器加载 HTML 后,会先解析静态文档。

因为 #app 里已经有完整 DOM,所以用户可以立即看到页面内容。这带来几个直接收益:

  1. 首屏更快可见。
  2. 对 SEO 更友好。
  3. JS 加载失败时仍有基础内容兜底。
  4. 用户不会在运行时脚本加载前看到空白页面。

这时用户看到的是 schema.data.ssg 生成的静态快照。

React 运行时如何接管页面

HTML 底部会继续加载运行时脚本,例如:

html
<script src="vendor.xxx.js"></script>
<script src="comp.xxx.js"></script>
<script src="index.xxx.js"></script>

入口脚本中会执行类似逻辑:

js
ReactDOM.render(
  React.createElement(App, null),
  document.querySelector('#app')
)

这里的关键点是:使用的是 ReactDOM.render,而不是 ReactDOM.hydrate。

这意味着 React 不会复用已有 SSG DOM 做 hydration,而是会把 React 生成的新 DOM 渲染到 #app 容器中。

更准确地说:

text
body 不会整体替换
#app 节点本身通常还在
#app 内部原有的 SSG 子 DOM 会被 React 渲染结果替换

React 渲染时会从 window._templateValue 中读取页面内容,例如:

js
_getSlotValue('title')
_getSlotValue('coverImage')
_getSlotValue('content')

正文内容也会作为富文本数据传给 Editor Kit 组件:

js
richtext: _getSlotValue('content')

由于 SSG HTML 和 React 运行时数据通常来自同一份 schema,React 重渲染后的页面看起来通常和初始 HTML 一致。用户视觉上可能感知不到替换,但 DOM 实际已经被 React 接管。

为什么不是标准 Hydration

标准 SSR/SSG + React hydration 一般会使用:

js
ReactDOM.hydrate(
  React.createElement(App, null),
  document.querySelector('#app')
)

hydrate 的目标是复用已有 DOM,只补齐事件绑定和 React 内部状态。

而当前页面使用的是:

js
ReactDOM.render(...)

因此当前机制不是严格意义上的 hydration,而是:

text
先展示 SSG 静态快照
再由 React 基于 _templateValue 重新 render
最后替换 #app 内部 DOM

这种方式实现成本相对低,对已有低代码运行时侵入较小,但会带来一次额外的 DOM 重建成本。

Template 修改带来的问题

原有流程可以覆盖运营修改 CMS data 的场景。

例如:

text
运营修改 CMS data
  ↓
重新保存 SSG
  ↓
重新打包发布页面

但是它有一个明显缺口:如果修改的是研发维护的 template,而不是运营填写的 data,依赖该 template 的页面不会自动重新保存 SSG。

这会导致:

text
template 已经发布新版本
页面 data 本身没变
schema.data.ssg 仍然是旧 template 生成的 HTML
线上初始 HTML 仍然使用旧结构
运行时 React 代码却已经是新 template 逻辑

结果就是首屏静态 HTML 和运行时 React 渲染结果可能不一致。

可能出现的问题包括:

  1. 首屏先展示旧 template 结构。
  2. React 执行后替换成新 template 结构。
  3. 页面出现闪动或布局跳变。
  4. SEO 抓取到的仍然是旧 HTML。
  5. 新旧结构差异较大时,可能出现样式错乱或运行时异常。

本质上,这是 template 发布没有触发 SSG 缓存失效导致的。

改动:Template 发布后批量刷新依赖页面

为了解决这个问题,我做了一个改动:

template 重新发布后,批量找到所有依赖该 template 的 published 状态页面,刷新这些页面的 SSG 缓存,并重新打包发布。

新的链路变成:

text
template 修改
  ↓
template 重新发布
  ↓
查询依赖该 template 的 published 页面
  ↓
批量刷新这些页面的 data.ssg
  ↓
批量重新打包发布页面
  ↓
线上 HTML 与最新 template 保持一致

这个改动的核心目标是:

text
template 代码
page schema
schema.data.ssg
线上 HTML
React runtime render

在 template 发布后重新对齐。

换句话说,template 发布不再只更新运行时代码,也会主动驱动依赖页面的 SSG 快照重建。

为什么只处理 Published 页面

批量刷新时只处理 published 状态页面,是为了避免误发布草稿内容。

低代码平台中通常存在多种页面状态,例如 draft、preview、published。template 发布时,如果把所有依赖页面都重新打包发布,可能会把还没准备好的草稿页面发布到线上。

因此更安全的策略是:

text
只对已经 published 的页面做 SSG 刷新和重新发布

这保证了改动只影响当前线上可访问页面。

这个改动解决了什么

这个机制补齐了 template 变更后的 SSG 失效链路。

改动前:

text
CMS data 变更 → 会刷新 SSG
template 变更 → 不会刷新 SSG

改动后:

text
CMS data 变更 → 刷新页面自身 SSG
template 变更 → 批量刷新依赖页面 SSG

这样可以避免线上出现「首屏是旧 template,React 接管后变成新 template」的不一致问题。

需要关注的工程细节

这个方案在工程落地时,需要关注几个点。

第一,批量任务需要可观测。

template 可能被大量页面依赖。发布后批量刷新 SSG 和重新打包,应该有明确的任务记录,包括成功、失败、重试次数和错误原因。

第二,任务最好异步化。

如果 template 依赖页面很多,同步刷新会拖慢 template 发布流程。更合理的方式是 template 发布成功后创建批量任务,由后台队列分批执行。

第三,需要失败重试。

某个页面刷新 SSG 或打包失败时,不应该影响其他页面。失败项应该记录下来,支持单独重试。

第四,需要限流和分批。

如果一次 template 发布影响大量页面,直接并发打包可能压垮构建服务。可以按批次、按队列优先级、按租户或业务线做限流。

第五,需要保证数据版本一致。

刷新 SSG 时应明确使用哪个 template 版本、哪个 page schema 版本、哪个运行时组件版本。否则仍可能出现静态 HTML 和运行时渲染不一致。

总结

当前低代码页面采用的是「SSG 静态快照 + React 运行时重渲染」的模式。

它的核心机制是:

text
低代码平台 preview 生成静态 HTML
SSG 缓存保存到 schema.data.ssg
构建时用 data.ssg 输出初始 HTML
浏览器先展示静态页面
运行时脚本加载后 ReactDOM.render 到 #app
React 基于 window._templateValue 重建页面并接管 #app

这个方案兼顾了首屏展示、SEO 和运行时交互能力。

但由于它依赖提前缓存的 data.ssg,所以任何影响页面静态结构的变更,都需要触发 SSG 失效与重建。

运营修改 CMS data 时,需要刷新页面自身 SSG。

template 修改并发布时,也需要刷新所有依赖该 template 的 published 页面 SSG,并重新打包发布。

这次改动的价值就在于:让 template 发布也能驱动依赖页面的 SSG 重建,保证线上初始 HTML 和 React 运行时渲染结果始终保持一致。

评论
0/100