WU / DOCSUNIVERSAL MICROFRONTENDS
ES

SSR e hidratación

Contrato render del servidor, Declarative Shadow DOM, estado de hidratación y helpers React/Vue.

Consulta las versiones, cambios y requisitos en Framework 4.0 y CLI 1.2.

Qué produce el servidor

wu-framework/server transforma el resultado de un renderer en el HTML de un contenedor WU. Por defecto encapsula contenido y estilos en Declarative Shadow DOM. El navegador puede materializar ese árbol antes de que cargue el JavaScript de la app.

El servidor recibe un módulo con render(props), no el lifecycle de montaje del cliente. El resultado puede ser un string HTML o { html, styles?, state? }. Quien genera el HTML debe escapar los datos no confiables antes de incorporarlos.

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 también acepta un módulo ya importado, una promesa, un módulo default o una función que lo carga. El host decide qué módulos puede ejecutar en el servidor; no conviertas un input de usuario en un import arbitrario.

Resultado y opciones

Elemento Contrato
result.html Contenedor completo
result.inner Interior declarativo o plano
result.containerAttrs ID, data-wu-app, data-wu-ssr, clase opcional
result.state Estado opcional devuelto por renderer
containerId Por defecto wu-app-<name>
shadowMode open por defecto; closed impide hidratación normal
declarative true por defecto
styles CSS extra del host para esa app
nonce Nonce CSP para estilos de esa respuesta

renderApps procesa varias apps en paralelo. Por defecto registra fallos individuales en errors y conserva las otras; failFast: true permite abortar. Un renderer que falla en renderApp produce un error con código WU_SSR_RENDER_FAILED.

Hidratar sin borrar

En el cliente, el shell registra la app normalmente y monta en su contenedor SSR. El sandbox detecta data-wu-ssr y la raíz data-wu-root. Si existe lifecycle.hydrate, el core lo invoca con las props de servidor y la facade scoped. Sin ese slot llama mount y el HTML previo funciona como contenido inicial hasta que el renderer lo reemplace.

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

Para React, hydrateRoot debe recibir el mismo árbol/props que produjo el servidor. Para Vue, el adapter usa createSSRApp y .mount para adoptar el DOM existente. El estado publicado debe conservar esa correspondencia; cambiar datos entre render e hidratación causa mismatches.

Helpers de renderer

El subpath server exporta renderReact, renderPreact, renderVue, renderSvelte, renderSolid y renderHtml. Los helpers devuelven un módulo conforme al contrato render.

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

Inyectar renderer y runtime evita resolver otra copia de React fuera del bundle del servidor. Vue admite inyección de createSSRApp y renderToString, además de setup. Los nombres de opciones server no son idénticos a runtime: { React, ReactDOM } del adapter cliente.

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}
/>

El shell aún debe inicializar el registro cliente correspondiente. El componente publica estado como JSON para hidratar; hydrate es true por defecto. fallbackToClient también es true: un fallo SSR deja el slot para un montaje cliente, salvo que lo desactives como en el ejemplo.

En una build estática de Astro el renderer corre al compilar; no convierte esa página en contenido personalizado por petición. Para datos dependientes del usuario necesitas una estrategia de servidor o carga cliente adecuada.

Estado y cabeceras

serializeState y renderStateScript resuelven la serialización para el contexto HTML/script. escapeHtml escapa texto, no valida una aplicación HTML arbitraria. No inyectes secretos en estado de hidratación: todo lo publicado en esa página es visible para su lector.

createNonce() produce un nonce para una respuesta. securityHeaders, applySecurityHeaders, wuSecurityMiddleware y withSecurityHeaders ayudan a instalar CSP y otras cabeceras. Usa el mismo nonce de respuesta en estilos/scripts que deban ejecutarse y un nonce nuevo en la siguiente respuesta; el servidor controla esa coordinación.

Referencia de implementación

Exports: src/server/index.js; render: src/server/wu-server-render.js; varios renders: src/server/wu-server-render.js; tipos: src/server/index.d.ts; adopción de DOM: src/core/wu-sandbox.js; slot hydrate: src/core/wu-core.js; Astro: integrations/astro/WuAppSSR.astro. Pruebas: tests/unit/server-render.test.js, tests/unit/server-security-fuzz.test.js, tests/unit/security-headers.test.js.

Para aislar cada petición y limitar trabajo, consulta createServerRequest, loaders privados, cancelación y concurrencia en la referencia SSR. Los wrappers de cabeceras 4.0 rechazan un nonce fijo: usa una función por petición o la generación predeterminada.

WU SE CONSTRUYE CONTIGO

¿Encontraste un bug? ¿Tienes una idea?

Tu experiencia mejora WU. Cuéntanos qué falla o ayuda a sostener su desarrollo.