SSR(Server-Side Rendering,服务端渲染)同步输出页面时,不一定会在 HTML 底部附带一大段 JSON。更准确地说:SSR 的核心任务是先在服务器端生成可访问、可展示的 HTML;底部 JSON 通常是为了把服务端已经拿到的数据交给客户端 JavaScript 继续使用,它是实现选择,不是 SSR 的硬性要求。

SSR 同步渲染一定会在页面底部输出大段 JSON 吗?

先给结论:底部 JSON 是可选的

如果页面只是静态展示,或者交互逻辑不需要复用服务端数据,服务器完全可以只返回渲染后的 HTML,而不额外输出大块 JSON。用户仍然能看到首屏内容,搜索引擎也能读取页面主体。

但在 React、Vue、Nuxt、Next.js 等需要客户端接管的场景里,框架经常会把初始状态写进页面,例如 window.__INITIAL_STATE____NEXT_DATA__type="application/json" 的脚本块。这样客户端启动后就能复用同一份数据,避免重新请求一次接口,并让页面从“已渲染”平滑进入“可交互”状态。

为什么有些 SSR 页面底部会有大段 JSON

常见原因主要有三类:

  1. 用于 Hydration:客户端框架需要知道服务端渲染时使用了哪些 props、路由信息和状态,才能把事件绑定到已有 HTML 上。
  2. 避免重复请求:服务端已经查过数据库或接口,如果客户端启动后再查一遍,会增加延迟和后端压力。
  3. 保留页面上下文:分页、筛选条件、用户态、推荐列表、AB 实验参数等信息,可能需要随页面一起交给浏览器。

所以,看到底部 JSON 并不代表“同步渲染必然如此”,它更多说明这个页面还承担了客户端应用接管、状态恢复或数据复用的任务。

几种常见实现方式

方式一:只返回 HTML,不注入初始 JSON

适合内容展示型页面、交互很少的专题页、文章页,或者客户端不需要立即接管状态的页面。

<body>
  <article>
    <h1>文章标题</h1>
    <p>这里是服务端已经渲染好的正文内容。</p>
  </article>
</body>

方式二:把必要初始数据写入页面

适合需要客户端继续渲染、绑定事件、复用首屏数据的页面。注意这里的数据应该尽量精简,只放客户端真正需要的字段。

<div id="app"><!-- 服务端渲染后的 HTML --></div>

<script>
  window.__INITIAL_STATE__ = {
    title: "Hello SSR",
    contentId: 1087
  };
</script>

方式三:首屏 HTML + 客户端按需请求接口

如果数据较大、实时性强,或者不适合直接暴露在 HTML 中,可以只输出首屏必要内容,后续交互再由浏览器请求 API。

fetch("/api/article/1087")
  .then(response => response.json())
  .then(data => {
    // 按需更新页面状态
  });

是否应该输出 JSON,要看这几个判断点

可以按下面的标准取舍:

  • 页面是否需要 Hydration:需要客户端框架接管时,通常要传递一部分初始状态。
  • 数据是否会被客户端继续使用:如果只用于生成 HTML,没必要完整塞到页面底部。
  • 数据体积是否过大:大 JSON 会增加 HTML 体积,影响首包和解析成本。
  • 是否包含敏感字段:任何写进 HTML 的数据都默认会被用户看到,后台字段、权限信息、内部标识要先过滤。
  • 缓存策略是否匹配:HTML 与 JSON 混在一起时,页面缓存更新会变得更重;拆成 API 后可分别缓存。

实践建议

对于普通内容页,可以优先保证服务端 HTML 完整、语义清晰,再只输出客户端接管所需的最小状态。不要把数据库原始字段、完整接口响应或后台配置一股脑塞进页面底部。

如果页面交互复杂,可以保留初始 JSON,但建议做字段裁剪、压缩和安全过滤;如果数据非常大,则更适合拆成接口按需加载。这样既能保留 SSR 的首屏优势,也能避免 HTML 被无意义的数据拖慢。

总之,SSR 同步渲染和“页面底部大段 JSON”不是绑定关系。前者解决首屏 HTML 的生成问题,后者解决客户端状态交接问题。是否需要这段 JSON,应该由页面交互、框架机制、性能预算和安全要求共同决定。