# Server and SSR recipes

Render HTML, React, and Vue with WU in Node; verify escaping, state, CSP, errors, and real HTTP responses.

<span id="de-un-renderer-a-una-respuesta-http"></span>

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

## From renderer to HTTP response

WU's server API receives a module with `render(props)` and returns a micro-app's markup. The HTTP host remains responsible for loading data, choosing trusted modules, and constructing the response. You do not need to initialize the browser runtime to use `wu-framework/server`.

The project includes a complete recipe in `examples/ssr/render-page.mjs`. It composes three real renderers—HTML, React, and Vue—and generates a document with Declarative Shadow DOM, hydration state, and CSP headers.

You can [download the runnable recipes](/examples/wu-server-recipes.zip), extract the ZIP, and run `npm install`. It includes code, tests, and an independent manifest to reproduce them outside the portal.

```sh
node examples/ssr/render-page.mjs Ada
node examples/ssr/server.mjs
```

The first command writes HTML to stdout. The second opens `http://127.0.0.1:4310/?name=Ada`. Run the commands from the project root with dependencies installed. The server accepts the `PORT` variable and shuts down with `Ctrl+C`.

<span id="un-renderer-html-pequeño"></span>

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

## A small HTML renderer

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

const greeting = {
  render({ name }) {
    return {
      html: `<p>Hola, ${escapeHtml(name)}.</p>`,
      styles: 'p { color: #b7d900; }',
      state: { name },
    };
  },
};

const result = await renderApp('greeting', {
  module: greeting,
  props: { name: 'Ada' },
});
```

The complete container is in `result.html`. Its shape is:

```html
<div id="wu-app-greeting" data-wu-app="greeting" data-wu-ssr="open">
  <template shadowrootmode="open">
    <style>p { color: #b7d900; }</style>
    <div data-wu-root><p>Hola, Ada.</p></div>
  </template>
</div>
```

`result.inner` contains the container's interior; `result.containerAttrs` lets Astro or another engine construct the outer element. `state` contains only what the renderer explicitly returns. Custom HTML renderers decide which data to publish; React and Vue helpers return props as state.

<span id="react-y-vue-sin-compilar-jsx-ni-sfc"></span>

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

## React and Vue without compiling JSX or SFCs

This recipe uses `React.createElement` and `h` so Node can run it directly. In a component project, you can import the equivalent server build output.

```js
import * as React from 'react';
import { renderToString as reactToString } from 'react-dom/server';
import { createSSRApp, h } from 'vue';
import { renderToString as vueToString } from '@vue/server-renderer';
import { renderReact, renderVue, renderApps } from 'wu-framework/server';

const reactApp = renderReact(
  ({ name }) => React.createElement('p', null, `Hola, ${name}`),
  { react: React, renderToString: reactToString },
);
const vueApp = renderVue(
  { props: ['name'], setup: props => () => h('p', `Hola, ${props.name}`) },
  { createSSRApp, renderToString: vueToString },
);

const result = await renderApps([
  { name: 'react-greeting', module: reactApp, props: { name: 'Ada' } },
  { name: 'vue-greeting', module: vueApp, props: { name: 'Ada' } },
]);
```

Renderer injection retains the same React or Vue instance used by the host. Public helper imports are `wu-framework/server` and `wu-framework/server/adapters`; `wu-framework/server/react` and `wu-framework/server/vue` subpaths do not exist in the package exports map.

<span id="estado-y-csp-por-respuesta"></span>

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

## State and CSP per response

```js
import { createNonce, renderApps, renderStateScript, securityHeaders } from 'wu-framework/server';

const nonce = createNonce();
const rendered = await renderApps(apps, { nonce, failFast: true });
const headers = securityHeaders({ nonce });
const body = rendered.html + renderStateScript(rendered.state, { nonce });
```

Use the same nonce in styles, the state script, and the CSP header for that response; generate a new one for the next response. `renderStateScript` publishes `window.__WU_SSR_STATE__` by default. Its `merge: true` option preserves entries from other apps when each integration emits its own script.

The local HTTP recipe uses `securityHeaders({ nonce, https: false, hsts: false })` because it listens over HTTP on loopback. For an HTTPS server, configure the corresponding transport policy. The helper does not start TLS or manage certificates.

Do not publish secrets in state: response HTML and hydration props are visible to the client.

<span id="escapar-no-es-sanitizar-un-documento"></span>

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

## Escaping is not document sanitization

`escapeHtml` converts special characters in a text value. `serializeState` preserves valid JSON and neutralizes `<`, `>`, U+2028, and U+2029 to prevent data from closing the state script. The test supplies `</script><img ...>` as a name and verifies that no executable tag appears.

HTML returned by `render()` is inserted as-is. If you receive external HTML, sanitize it before passing it to the renderer with a policy appropriate to the product; `renderApp` does not remove dangerous tags or attributes from that HTML. Renderer CSS also belongs to trusted code.

State must be JSON-serializable. Circular references and `BigInt` fail in `serializeState`; functions and `undefined` properties are not portable hydration state.

<span id="fallos-parciales-y-failfast"></span>

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

## Partial failures and failFast

```js
import { renderApps } from 'wu-framework/server';

const result = await renderApps([
  { name: 'healthy', module: { render: () => '<p>Disponible</p>' } },
  { name: 'optional', module: { render() { throw new Error('Sin datos'); } } },
]);

console.log(result.errors[0].app); // optional
console.log(result.byApp.optional);
// <div id="wu-app-optional" data-wu-app="optional"></div>
```

`renderApps` preserves input order despite parallel rendering. Without `failFast`, it returns HTML from healthy apps, empty containers for failures, and an `errors` list. The host decides whether to return 200 with visible degradation or change the HTTP status.

With `failFast: true`, the promise rejects. This does not automatically cancel other renderers that have already started; avoid work with external effects inside `render`.

| Failure | Result |
| --- | --- |
| Invalid name | Rejection before rendering; maximum 50 characters, alphanumeric start followed by alphanumeric characters, `_`, or `-` |
| Missing `module` | Error requesting the renderer |
| Module without `render` | Resolution error |
| `render` throws | Error with `code: WU_SSR_RENDER_FAILED`, `appName`, and `cause` |
| `shadowMode` other than `open`/`closed` | Options error |
| Invalid nonce or `varName` | Error before emitting the corresponding attribute or script |
| Unsupported `render` result | Normalization error; does not necessarily carry the renderer failure code |

<span id="qué-falta-para-hidratar"></span>

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

## What remains for hydration

The recipe produces real HTML without downloading app JavaScript. To make it interactive, register the same apps on the client and mount them in their SSR containers. A lifecycle with `hydrate` adopts the existing DOM; without it, WU uses `mount`. React and Vue adapters must receive the same tree and props as the server.

`shadowMode: 'closed'` is suitable for inert markup: normal hydration cannot access that root. `declarative: false` produces flat markup with `data-wu-ssr="flat"`, useful when you do not need declarative encapsulation.

See [SSR and hydration](/docs/framework/ssr/) and [the lifecycle](/docs/framework/lifecycle/) to connect both sides. A WASM/WASI module can supply portable renderer logic through a host runner; alone, it does not replace the HTTP server, data resolution, or response policy. The [A service connected through WU](/docs/guides/browser-backend/) guide covers requests, contracts, and cancellation between applications in the browser.

<span id="ejecutar-las-comprobaciones"></span>

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

## Run the checks

```sh
node scripts/verify-server-examples.mjs
```

The suite checks all three renderers, nonce/CSP consistency, state, escaping, invalid options, failure codes/context, partial degradation, and flat output. Tests use exports from the installed package, not copies of its functions.