# Rendering and HTTP security reference

Complete contracts for SSR, server adapters, serialization, and headers.

<span id="servidor-y-navegador-tienen-responsabilidades-distintas"></span>

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

## Server and browser have different responsibilities

Import these functions from `wu-framework/server`, `wu-framework/server/adapters`, or `wu-framework/server/security`. Rendering receives explicit server modules; it does not mount browser components or automatically download a manifest's entry.

Each request must retain its own state and nonce. Renderer HTML is trusted content: use `escapeHtml` for external data and avoid sending secrets through `renderStateScript`. The [server recipe](/docs/framework/server-recipes/) shows how to compose the response and hydrate it.

The names `RenderResult` and `RenderOutput` on this page describe result shapes; check the `server` subpath declarations to import types that are actually exported.

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

## renderApp

Renders an application with a server module and produces the container recognized by WU hydration.

`renderApp(appName, options): Promise<RenderResult>`

- **Parameters:** Nonempty name; required options.module; props, styles, containerId, className, shadowMode, and declarative; optional nonce.
- **Returns:** Object with html, inner, containerAttrs, state, appName, and containerId.
- **Errors:** Propagates input or renderer errors; handle rejection before sending the HTTP response.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { renderApp, renderHtml, escapeHtml } from 'wu-framework/server';
const card = renderHtml(({ title }) => `<h2>${escapeHtml(title)}</h2>`);
const result = await renderApp('card', { module: card, props: { title: 'Resumen' } });
console.log(result.html);
```

The module must produce trusted HTML. The helper escapes attributes and state, but does not sanitize the HTML returned by render. It does not automatically import a browser URL.

Reference: `src/server/wu-server-render.js`.

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

## renderApps

Renders a list in parallel and collects HTML, state, and errors per application.

`renderApps(apps, options?): Promise<RenderAppsResult>`

- **Parameters:** List of {name, module, ...options}; failFast defaults to false; optional shared nonce.
- **Returns:** {html, byApp, state, errors}; preserves partial results with failFast false.
- **Errors:** Records errors per entry by default; failFast:true rejects the operation. Does not cancel renderers that are already running.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { renderApps, renderHtml } from 'wu-framework/server';
const result = await renderApps([
  { name: 'summary', module: renderHtml(() => '<p>Resumen</p>') },
  { name: 'activity', module: renderHtml(() => '<p>Actividad</p>') },
]);
console.log(result.errors);
```

Reference: `src/server/wu-server-render.js`.

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

## renderStateScript

Serializes state into a script the client can retrieve during hydration.

`renderStateScript(state, options?): string`

- **Parameters:** Serializable state; varName JavaScript identifier, merge boolean, optional nonce.
- **Returns:** Complete script tag.
- **Errors:** Rejects invalid varName, invalid nonce, or values that JSON.stringify cannot serialize.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { renderStateScript } from 'wu-framework/server';
const script = renderStateScript({ card: { title: 'Resumen' } }, { merge: true });
```

Do not include secrets: this state is visible in the response HTML.

Reference: `src/server/wu-server-render.js`.

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

## declarativeShadowDomPolyfill

Generates a compatibility script for templates with shadowrootmode when native support is unavailable.

`declarativeShadowDomPolyfill(options?): string`

- **Parameters:** Optional options.nonce.
- **Returns:** Script HTML; calling the helper does not execute it.
- **Errors:** An invalid nonce fails during attribute construction.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { declarativeShadowDomPolyfill } from 'wu-framework/server';
const script = declarativeShadowDomPolyfill({ nonce });
```

Emit the script after the content it must convert; it does not observe future templates.

Reference: `src/server/wu-server-render.js`.

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

## escapeHtml

Escapes ampersands, tag delimiters, and quotes for HTML text or attributes.

`escapeHtml(value): string`

- **Parameters:** Any value; null and undefined become an empty string.
- **Returns:** Escaped string.
- **Errors:** String coercion may propagate an error from the value itself.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { escapeHtml } from 'wu-framework/server';
escapeHtml('<etiqueta>'); // '&lt;etiqueta&gt;'
```

Does not validate URLs or turn arbitrary HTML into safe content.

Reference: `src/server/wu-server-render.js`.

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

## serializeState

Encodes JSON and prevents tag characters from closing the script.

`serializeState(value): string`

- **Parameters:** JSON-compatible value; root null/undefined is represented as null.
- **Returns:** JSON with <, >, and Unicode separators escaped.
- **Errors:** Cyclic references, BigInt, or unsupported serialization may throw.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { serializeState } from 'wu-framework/server';
serializeState({ title: '</script>' });
```

Reference: `src/server/wu-server-render.js`.

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

## SSR_MARKER_ATTR / SSR_ROOT_ATTR

Exposes the attribute names that coordinate the renderer and hydration.

`SSR_MARKER_ATTR: 'data-wu-ssr'; SSR_ROOT_ATTR: 'data-wu-root'`

- **Parameters:** Constants exported from wu-framework/server.
- **Returns:** Two strings.
- **Errors:** Reading the constants does not validate container contents.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { SSR_MARKER_ATTR, SSR_ROOT_ATTR } from 'wu-framework/server';
console.log(SSR_MARKER_ATTR, SSR_ROOT_ATTR);
```

Reference: `src/server/wu-server-render.js`.

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

## createNonce

Generates a 128-bit nonce with crypto.getRandomValues.

`createNonce(): string`

- **Parameters:** No parameters.
- **Returns:** Base64url string without padding.
- **Errors:** Requires Web Crypto; throws if it is unavailable.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { createNonce } from 'wu-framework/server';
const nonce = createNonce();
```

Reference: `src/server/wu-security-headers.js`.

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

## buildCsp

Composes a CSP policy for scripts, styles, images, connections, and frames.

`buildCsp(options?): string`

- **Parameters:** nonce; scriptSrc/styleSrc/connectSrc/imgSrc/frameSrc lists; unsafeInlineStyle; https defaults to true.
- **Returns:** Complete Content-Security-Policy value.
- **Errors:** Rejects an invalid nonce; origin lists must come from trusted configuration.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { buildCsp } from 'wu-framework/server';
const policy = buildCsp({ nonce, connectSrc: ['https://api.example.com'] });
```

Without nonce or styleSrc, the policy includes unsafe-inline for styles; connect-src also includes scriptSrc. https:false omits upgrade-insecure-requests.

Reference: `src/server/wu-security-headers.js`.

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

## securityHeaders

Builds CSP, HSTS, X-Frame-Options, MIME, referrer, permissions, and optional CORS headers.

`securityHeaders(options?): Record<string, string>`

- **Parameters:** CSP options plus hsts, frameOptions, referrerPolicy, permissionsPolicy, nosniff, corsOrigin, and csp.
- **Returns:** Header map; does not modify a response.
- **Errors:** Propagates CSP validation when generating the policy.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { securityHeaders } from 'wu-framework/server';
const headers = securityHeaders({ nonce, hsts: false, corsOrigin: 'https://shell.example.com' });
```

HSTS is enabled by default; explicitly disable it for an HTTP development response. csp:false omits CSP.

Reference: `src/server/wu-security-headers.js`.

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

## applySecurityHeaders

Applies headers to a Node response supporting setHeader.

`applySecurityHeaders(res, options?): response`

- **Parameters:** res with setHeader; getHeader allows merging an existing Vary header.
- **Returns:** The same response.
- **Errors:** Throws if res lacks setHeader or the policy is invalid.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { applySecurityHeaders } from 'wu-framework/server';
applySecurityHeaders(res, { nonce });
```

Reference: `src/server/wu-security-headers.js`.

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

## wuSecurityMiddleware

Creates Express or Connect middleware with a nonce per request.

`wuSecurityMiddleware(options?): middleware`

- **Parameters:** Header options; `nonce` must be a per-request function. Omitting it generates a fresh nonce automatically; a fixed string is rejected when constructing the wrapper.
- **Returns:** A (req,res,next) function that writes req.wuNonce and res.locals.nonce if locals exists.
- **Errors:** Nonce or header generation errors propagate when the middleware runs.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { wuSecurityMiddleware } from 'wu-framework/server';
app.use(wuSecurityMiddleware());
```

Reference: `src/server/wu-security-headers.js`.

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

## withSecurityHeaders

Wraps a Fetch handler and adds headers while preserving the body and HTTP status.

`withSecurityHeaders(handler, options?): (request) => Promise<Response>`

- **Parameters:** handler(request,{nonce}) must return Response.
- **Returns:** Asynchronous handler with a new Response.
- **Errors:** Propagates handler errors and requires a Response-compatible result.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { withSecurityHeaders } from 'wu-framework/server';
const GET = withSecurityHeaders(async (_request, { nonce }) => {
  return new Response(`<p>Listo</p>`, { headers: { 'Content-Type': 'text/html' } });
});
```

Reference: `src/server/wu-security-headers.js`.

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

## renderReact

Adapts the React renderer to the renderApp contract.

`renderReact(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **Parameters:** React Component; styles, strictMode, injectable react and renderToString.
- **Returns:** Server module with render returning html, styles, and state.
- **Errors:** Propagates input or renderer errors; handle rejection before sending the HTTP response.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { renderReact, renderApp } from 'wu-framework/server';
import React from 'react';
import { renderToString } from 'react-dom/server';
const module = renderReact(Card, { react: React, renderToString });
const result = await renderApp('card', { module, props: { count: 2 } });
```

Inject the same React instance the component uses to avoid duplicating its runtime.

Reference: `src/server/adapters.js`.

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

## renderVue

Adapts the Vue renderer to the renderApp contract.

`renderVue(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **Parameters:** Vue Component; styles, setup(app,props), injectable createSSRApp and renderToString.
- **Returns:** Server module with render returning html, styles, and state.
- **Errors:** Propagates input or renderer errors; handle rejection before sending the HTTP response.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { renderVue, renderApp } from 'wu-framework/server';
const module = renderVue(Card, { setup(app) { /* app.use(plugin) */ } });
const result = await renderApp('card', { module, props: { count: 2 } });
```

Requires vue and @vue/server-renderer or both injected functions.

Reference: `src/server/adapters.js`.

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

## renderPreact

Adapts the Preact renderer to the renderApp contract.

`renderPreact(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **Parameters:** Preact Component; styles, injectable h and renderToString.
- **Returns:** Server module with render returning html, styles, and state.
- **Errors:** Propagates input or renderer errors; handle rejection before sending the HTTP response.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { renderPreact, renderApp } from 'wu-framework/server';
const module = renderPreact(Card, { styles: ['h2{color:navy}'] });
const result = await renderApp('card', { module, props: { count: 2 } });
```

Requires preact and preact-render-to-string if functions are not injected.

Reference: `src/server/adapters.js`.

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

## renderSvelte

Adapts the Svelte renderer to the renderApp contract.

`renderSvelte(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **Parameters:** Server-compiled component and optional styles.
- **Returns:** Server module with render returning html, styles, and state.
- **Errors:** Propagates input or renderer errors; handle rejection before sending the HTTP response.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { renderSvelte, renderApp } from 'wu-framework/server';
const module = renderSvelte(Card);
const result = await renderApp('card', { module, props: { count: 2 } });
```

Svelte 4 uses Component.render; Svelte 5 imports svelte/server. The wrapper does not add out.head to the document.

Reference: `src/server/adapters.js`.

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

## renderSolid

Adapts the Solid renderer to the renderApp contract.

`renderSolid(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **Parameters:** SSR-compiled component and optional styles.
- **Returns:** Server module with render returning html, styles, and state.
- **Errors:** Propagates input or renderer errors; handle rejection before sending the HTTP response.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { renderSolid, renderApp } from 'wu-framework/server';
const module = renderSolid(Card);
const result = await renderApp('card', { module, props: { count: 2 } });
```

Resolves solid-js/web in the server environment; does not accept an injected renderer as React does.

Reference: `src/server/adapters.js`.

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

## renderHtml

Adapts the Html renderer to the renderApp contract.

`renderHtml(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **Parameters:** A (props) => string|Promise<string> function and styles as string/list/function.
- **Returns:** Server module with render returning html, styles, and state.
- **Errors:** Propagates input or renderer errors; handle rejection before sending the HTTP response.
- **Permissions:** Does not use browser grants; the server application controls modules, data, and headers.
- **Cleanup:** Creates no listeners or persistent client runtime.

```js
import { renderHtml, renderApp } from 'wu-framework/server';
const module = renderHtml(({ count }) => `<p>${Number(count)}</p>`);
const result = await renderApp('card', { module, props: { count: 2 } });
```

Returned HTML is not sanitized. Escape external content before interpolating it.

Reference: `src/server/adapters.js`.

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

## createServerRequest

`createServerRequest(options?): WuServerRequest`

Creates a private per-request context with `signal`, `locals`, `renderApp`, `renderApps`, `stream` and `dispose`. It accepts batch options, including `request`, and combines its signal with `options.signal`. Do not create this object at module scope. `dispose(reason)` aborts, releases listeners and clears `locals`.

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

export const GET = withSecurityHeaders(async (request, { nonce }) => {
  const scope = createServerRequest({ request, nonce, timeout: 2000, concurrency: 2 });
  try {
    scope.locals.set('requestId', crypto.randomUUID());
    const rendered = await scope.renderApps([{
      name: 'greeting',
      load: async ({ signal }) => {
        signal.throwIfAborted();
        return { props: { label: 'Hello' }, publicState: { label: 'Hello' } };
      },
      module: { render: props => '<p>' + escapeHtml(props.label) + '</p>' },
    }]);
    return new Response(rendered.html + renderStateScript(rendered.state, { nonce }), {
      headers: { 'Content-Type': 'text/html; charset=utf-8' },
    });
  } finally {
    scope.dispose();
  }
});
```

`load(context)` receives `{ appName, signal, request, locals }` and returns `{ props?, publicState? }`. With a loader, only `publicState` becomes hydration state; neither private props nor the renderer’s returned `state` is published automatically. Without a loader, the renderer’s `state` is retained. This does not prevent the renderer itself from writing a secret into HTML.

<span id="cancelación-plazos-y-concurrencia-ssr"></span>

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

## SSR cancellation, deadlines and concurrency

`renderApp`, `renderApps` and `renderAppsStream` accept `signal` and per-app `timeout`, including the loader; `timeout: 0` disables the deadline. Batches and streams accept `concurrency`, unbounded by default. Aborting preserves `signal.reason`; without a supplied reason it uses `WU_SSR_ABORTED`. An expired deadline produces `WU_SSR_TIMEOUT`. Renderers and loaders must cooperate with `context.signal` to stop their work.

Without `failFast`, an app failure allows partial degradation; with `failFast: true`, the batch is aborted. Canceling the stream propagates an abort to pending work. When returning an HTTP stream, retain the context until consumption finishes or is canceled; do not call `dispose()` immediately after constructing the response.

```js
// Reusable wrappers: a function generates a new nonce for each request.
wuSecurityMiddleware({ nonce: () => createNonce() });
withSecurityHeaders(handler, { nonce: () => createNonce() });
// Low-level helpers still accept the nonce of this response.
const nonce = createNonce();
const headers = securityHeaders({ nonce });
```