# SSR 与水合

服务器 render 契约、Declarative Shadow DOM、水合状态及 React/Vue helpers。

版本、变更与要求见 [Framework 3.2 与 CLI 1.0](/zh-cn/docs/guides/releases/)。


<span id="qué-produce-el-servidor"></span>

<span class="section-anchor" id="section-1"></span>

## 服务器生成什么

`wu-framework/server` 将 renderer 结果转换为 WU 容器的 HTML。默认使用 Declarative Shadow DOM 封装内容和样式。浏览器可以在应用 JavaScript 加载前创建该树。

服务器接收具有 `render(props)` 的模块，而不是客户端挂载生命周期。结果可以是 HTML 字符串，也可以是 `{ html, styles?, state? }`。HTML 生成方必须在加入不可信数据前进行转义。

```js
import { renderApp, escapeHtml, renderStateScript } from 'wu-framework/server';

const result = await renderApp('greeting', {
  props: { name: 'Ada' },
  module: {
    render(props) {
      return {
        html: `<p>Hola, ${escapeHtml(props.name)}</p>`,
        styles: 'p { color: #f97316; }',
        state: props,
      };
    },
  },
});

const html = result.html + renderStateScript({ greeting: result.state });
```

`renderApp` 还接受已导入模块、Promise、`default` 模块或加载模块的函数。宿主决定服务器上可执行哪些模块；不要把用户输入转换为任意 import。

<span id="resultado-y-opciones"></span>

<span class="section-anchor" id="section-2"></span>

## 结果与选项

| 项目 | 行为约定 |
| --- | --- |
| `result.html` | 完整容器 |
| `result.inner` | 声明式或平面内部内容 |
| `result.containerAttrs` | ID、`data-wu-app`、`data-wu-ssr`、可选 class |
| `result.state` | renderer 返回的可选状态 |
| `containerId` | 默认为 `wu-app-<name>` |
| `shadowMode` | 默认为 `open`；closed 阻止常规水合 |
| `declarative` | 默认为 `true` |
| `styles` | 宿主为该应用添加的 CSS |
| `nonce` | 此响应样式的 CSP nonce |

`renderApps` 并行处理多个应用。默认将单个应用的故障记录在 `errors` 中，并保留其他结果；`failFast: true` 允许提前拒绝。renderer 在 `renderApp` 中失败会产生错误码为 `WU_SSR_RENDER_FAILED` 的错误。

<span id="hidratar-sin-borrar"></span>

<span class="section-anchor" id="section-3"></span>

## 保留 DOM 完成水合

客户端 shell 正常注册应用，并挂载到 SSR 容器。sandbox 检测 `data-wu-ssr` 和 `data-wu-root` 根节点。如果存在 `lifecycle.hydrate`，core 会传入服务器 props 和 scoped facade 并调用它。没有该 slot 时调用 `mount`；原有 HTML 作为初始内容保留，直到 renderer 替换它。

```js
wu.define('greeting', {
  mount(container) {
    container.textContent = 'Montaje cliente';
  },
  hydrate(container, { props }) {
    // Adoptar lo que ya existe. No limpiar container primero.
    const paragraph = container.querySelector('p');
    paragraph?.setAttribute('aria-label', `Saludo para ${props.name}`);
  },
  unmount(container) {
    container.replaceChildren();
  },
});
```

对于 React，`hydrateRoot` 必须接收与服务器生成内容相同的树和 props。对于 Vue，adapter 使用 `createSSRApp` 和 `.mount` 接管已有 DOM。发布的状态必须保持这种对应；渲染与水合之间改变数据会导致不匹配。

<span id="helpers-de-renderer"></span>

<span class="section-anchor" id="section-4"></span>

## Renderer helpers

server subpath 导出 `renderReact`、`renderPreact`、`renderVue`、`renderSvelte`、`renderSolid` 和 `renderHtml`。这些 helpers 返回符合 `render` 契约的模块。

```jsx
import * as React from 'react';
import { renderToString } from 'react-dom/server';
import { renderReact } from 'wu-framework/server';
import App from './App.jsx';

export default renderReact(App, {
  react: React,
  renderToString,
  styles: '.greeting { font-weight: 600; }',
});
```

注入 renderer 和运行时可避免在服务器 bundle 外解析另一份 React。Vue 支持注入 `createSSRApp` 和 `renderToString`，以及 `setup`。服务器选项名称与客户端 adapter 的 `runtime: { React, ReactDOM }` 不完全相同。

<span class="section-anchor" id="section-5"></span>

## Astro

```astro
---
import WuAppSSR from 'wu-framework/integrations/astro/WuAppSSR.astro';
import greetingServer from '../apps/greeting/server.js';
---
<WuAppSSR
  name="greeting"
  module={greetingServer}
  props={{ name: 'Ada' }}
  fallbackToClient={false}
/>
```

shell 仍需初始化对应的客户端注册。组件将状态发布为 JSON 以进行水合；`hydrate` 默认为 `true`。`fallbackToClient` 也默认为 `true`：SSR 失败会留下 slot 供客户端挂载，除非像示例中一样禁用它。

在 Astro 静态构建中，renderer 在编译时运行；这不会让页面按请求生成个性化内容。依赖用户的数据需要合适的服务器策略或客户端加载。

<span id="estado-y-cabeceras"></span>

<span class="section-anchor" id="section-6"></span>

## 状态与响应头

`serializeState` 和 `renderStateScript` 处理 HTML/script 上下文的序列化。`escapeHtml` 转义文本，不验证任意 HTML 应用。不要将秘密注入水合状态：页面上发布的所有内容对读者可见。

`createNonce()` 为一个响应生成 nonce。`securityHeaders`、`applySecurityHeaders`、`wuSecurityMiddleware` 和 `withSecurityHeaders` 帮助安装 CSP 和其他响应头。需要执行的样式与脚本应使用同一响应 nonce，下个响应使用新 nonce；服务器负责协调。

<span id="referencia-de-implementación"></span>

<span class="section-anchor" id="section-7"></span>

## 实现参考

导出：`src/server/index.js`；render：`src/server/wu-server-render.js`；多应用渲染：`src/server/wu-server-render.js`；类型：`src/server/index.d.ts`；DOM 接管：`src/core/wu-sandbox.js`；hydrate slot：`src/core/wu-core.js`；Astro：`integrations/astro/WuAppSSR.astro`。测试：`tests/unit/server-render.test.js`、`tests/unit/server-security-fuzz.test.js`、`tests/unit/security-headers.test.js`。

要隔离每个请求并限制工作，请查阅 [SSR 参考](/zh-cn/docs/framework/api-server/)中的 `createServerRequest`、私有 loaders、取消和并发。4.0 响应头 wrappers 拒绝固定 nonce：请使用每次请求调用的函数或默认生成方式。