Rendering and HTTP security reference
Complete contracts for SSR, server adapters, serialization, and headers.
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 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
import { escapeHtml } from 'wu-framework/server';
escapeHtml('<etiqueta>'); // '<etiqueta>'
Does not validate URLs or turn arbitrary HTML into safe content.
Reference: src/server/wu-server-render.js.
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.
import { serializeState } from 'wu-framework/server';
serializeState({ title: '</script>' });
Reference: src/server/wu-server-render.js.
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.
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.
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.
import { createNonce } from 'wu-framework/server';
const nonce = createNonce();
Reference: src/server/wu-security-headers.js.
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.
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.
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.
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.
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.
import { applySecurityHeaders } from 'wu-framework/server';
applySecurityHeaders(res, { nonce });
Reference: src/server/wu-security-headers.js.
wuSecurityMiddleware
Creates Express or Connect middleware with a nonce per request.
wuSecurityMiddleware(options?): middleware
- Parameters: Header options;
noncemust 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.
import { wuSecurityMiddleware } from 'wu-framework/server';
app.use(wuSecurityMiddleware());
Reference: src/server/wu-security-headers.js.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
renderHtml
Adapts the Html renderer to the renderApp contract.
renderHtml(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }
- Parameters: A (props) => string|Promise
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.
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.
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.
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.
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.
// 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 });