# Recetas de servidor y SSR

Renderiza HTML, React y Vue con WU en Node; comprueba escape, estado, CSP, errores y respuestas HTTP reales.

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

## De un renderer a una respuesta HTTP

El servidor de WU recibe un módulo con `render(props)` y devuelve el marcado de una micro-app. El host HTTP conserva la responsabilidad de cargar datos, elegir módulos confiables y construir la respuesta. No necesitas inicializar el runtime del navegador para usar `wu-framework/server`.

El proyecto incluye una receta completa en `examples/ssr/render-page.mjs`. Compone tres renderers reales —HTML, React y Vue— y genera un documento con Declarative Shadow DOM, estado de hidratación y cabeceras CSP.

Puedes [descargar las recetas ejecutables](/examples/wu-server-recipes.zip), extraer el ZIP y ejecutar `npm install`. Incluye el código, las pruebas y un manifest independiente para reproducirlas fuera del portal.

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

La primera orden escribe HTML en stdout. La segunda abre `http://127.0.0.1:4310/?name=Ada`. Los comandos se ejecutan desde la raíz del proyecto con sus dependencias instaladas. El servidor acepta la variable `PORT` y se cierra con `Ctrl+C`.

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

## Un renderer HTML pequeño

```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' },
});
```

El contenedor completo está en `result.html`. Su forma es:

```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` contiene el interior del contenedor; `result.containerAttrs` permite que Astro u otro motor construyan el elemento exterior. `state` sólo contiene lo devuelto explícitamente por el renderer. Los renderers HTML personalizados deciden qué datos publicar; los helpers React y Vue devuelven las props como estado.

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

## React y Vue sin compilar JSX ni SFC

Esta receta usa `React.createElement` y `h` para que Node pueda ejecutarla directamente. En un proyecto de componentes puedes importar el resultado del build de servidor equivalente.

```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' } },
]);
```

La inyección del renderer mantiene la misma instancia de React o Vue que utiliza el host. Los imports públicos de los helpers son `wu-framework/server` y `wu-framework/server/adapters`; no existen subpaths `wu-framework/server/react` o `wu-framework/server/vue` en el mapa de exports del paquete.

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

## Estado y CSP por respuesta

```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 });
```

Usa el mismo nonce en estilos, script de estado y cabecera CSP de esa respuesta; genera uno nuevo para la siguiente. `renderStateScript` publica por defecto `window.__WU_SSR_STATE__`. Su opción `merge: true` conserva entradas de otras apps cuando cada integración emite su propio script.

La receta HTTP local usa `securityHeaders({ nonce, https: false, hsts: false })` porque escucha por HTTP en loopback. Para un servidor HTTPS configura la política de transporte correspondiente. El helper no inicia TLS ni administra certificados.

No publiques secretos en el estado: el HTML de la respuesta y las props de hidratación son visibles al cliente.

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

## Escapar no es sanitizar un documento

`escapeHtml` convierte los caracteres especiales de un valor de texto. `serializeState` conserva JSON válido y neutraliza `<`, `>`, U+2028 y U+2029 para impedir que un dato cierre el script de estado. La prueba introduce `</script><img ...>` como nombre y comprueba que no aparece una etiqueta ejecutable.

El HTML devuelto por `render()` se inserta tal cual. Si recibes HTML externo, sanitízalo antes de entregarlo al renderer con una política adecuada al producto; `renderApp` no elimina etiquetas ni atributos peligrosos de ese HTML. El CSS del renderer también pertenece a código confiable.

El estado debe ser serializable como JSON. Referencias circulares y `BigInt` fallan en `serializeState`; funciones y propiedades `undefined` no constituyen estado de hidratación portable.

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

## Fallos parciales y 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` conserva el orden de las entradas aunque renderice en paralelo. Sin `failFast`, devuelve HTML de las apps sanas, contenedores vacíos para los fallos y una lista `errors`. El host decide si responde 200 con una degradación visible o cambia el estado HTTP.

Con `failFast: true`, la promesa rechaza. Eso no cancela automáticamente los otros renderers que ya empezaron; evita trabajo con efectos externos dentro de `render`.

| Fallo | Resultado |
| --- | --- |
| Nombre inválido | Rechazo antes de renderizar; máximo 50 caracteres, inicio alfanumérico y resto alfanumérico, `_` o `-` |
| Falta `module` | Error que solicita el renderer |
| Módulo sin `render` | Error de resolución |
| `render` lanza | Error con `code: WU_SSR_RENDER_FAILED`, `appName` y `cause` |
| `shadowMode` distinto de `open`/`closed` | Error de opciones |
| Nonce o `varName` inválidos | Error antes de emitir el atributo o script correspondiente |
| Resultado no admitido de `render` | Error de normalización; no tiene necesariamente el código de fallo del renderer |

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

## Qué falta para hidratar

La receta produce HTML real sin descargar JavaScript de las apps. Para hacerlo interactivo, registra las mismas apps en el cliente y móntalas en sus contenedores SSR. Un lifecycle con `hydrate` adopta el DOM existente; sin él, WU usa `mount`. Los adapters de React y Vue deben recibir el mismo árbol y las mismas props del servidor.

`shadowMode: 'closed'` sirve para marcado inerte: la hidratación normal no puede acceder a esa raíz. `declarative: false` produce marcado plano con `data-wu-ssr="flat"`, útil cuando no necesitas encapsulación declarativa.

Consulta [SSR e hidratación](/es/docs/framework/ssr/) y [el lifecycle](/es/docs/framework/lifecycle/) para conectar ambos lados. Un módulo WASM/WASI puede aportar lógica portable al renderer a través de un runner del host; por sí solo no sustituye el servidor HTTP, la resolución de datos ni la política de respuesta. La guía [Un servicio conectado con WU](/es/docs/guides/browser-backend/) recorre solicitudes, contratos y cancelación entre aplicaciones desde el navegador.

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

## Ejecutar las comprobaciones

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

La suite comprueba los tres renderers, correspondencia entre nonce y CSP, estado, escape, opciones inválidas, código/contexto de fallos, degradación parcial y salida plana. Las pruebas usan los exports del paquete instalado, no copias de sus funciones.