SSR e hidratação
Contrato render do servidor, Declarative Shadow DOM, estado de hidratação e helpers React/Vue.
Veja versões, mudanças e requisitos em Framework 4.0 e CLI 1.2.
O que o servidor produz
wu-framework/server transforma o resultado de um renderizador no HTML de um contêiner WU. Por padrão, encapsula conteúdo e estilos em Declarative Shadow DOM. O navegador pode materializar essa árvore antes de o JavaScript da app carregar.
O servidor recebe um módulo com render(props), não o ciclo de vida de montagem do cliente. O resultado pode ser uma string HTML ou { html, styles?, state? }. Quem gera o HTML deve escapar os dados não confiáveis antes de incorporá-los.
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 também aceita um módulo já importado, uma promise, um módulo default ou uma função que o carrega. O host decide quais módulos pode executar no servidor; não transforme uma entrada do usuário em um import arbitrário.
Resultado e opções
| Elemento | Contrato |
|---|---|
result.html |
Contêiner completo |
result.inner |
Interior declarativo ou plano |
result.containerAttrs |
ID, data-wu-app, data-wu-ssr, classe opcional |
result.state |
Estado opcional retornado pelo renderizador |
containerId |
Por padrão, wu-app-<name> |
shadowMode |
open por padrão; closed impede a hidratação normal |
declarative |
true por padrão |
styles |
CSS extra do host para essa app |
nonce |
Nonce CSP para estilos dessa resposta |
renderApps processa várias apps em paralelo. Por padrão, registra falhas individuais em errors e mantém as outras; failFast: true permite abortar. Um renderizador que falha em renderApp produz um erro com código WU_SSR_RENDER_FAILED.
Hidratar sem apagar
No cliente, o shell registra a app normalmente e a monta no contêiner SSR. O sandbox detecta data-wu-ssr e a raiz data-wu-root. Se existir lifecycle.hydrate, o core o invoca com as props do servidor e a fachada com escopo. Sem esse slot, chama mount e o HTML anterior funciona como conteúdo inicial até o renderizador substituí-lo.
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();
},
});
No React, hydrateRoot deve receber a mesma árvore e props produzidas pelo servidor. No Vue, o adaptador usa createSSRApp e .mount para adotar o DOM existente. O estado publicado deve manter essa correspondência; alterar dados entre renderização e hidratação causa divergências.
Helpers de renderizador
O subcaminho server exporta renderReact, renderPreact, renderVue, renderSvelte, renderSolid e renderHtml. Os helpers retornam um módulo conforme o 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; }',
});
Injetar renderizador e runtime evita resolver outra cópia do React fora do bundle do servidor. Vue aceita a injeção de createSSRApp e renderToString, além de setup. Os nomes das opções do servidor não são idênticos a runtime: { React, ReactDOM } do adaptador 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}
/>
O shell ainda deve inicializar o registro cliente correspondente. O componente publica estado como JSON para hidratar; hydrate é true por padrão. fallbackToClient também é true: uma falha SSR deixa o slot para uma montagem cliente, a menos que você desative isso, como no exemplo.
Em um build estático do Astro, o renderizador é executado na compilação; isso não transforma a página em conteúdo personalizado por solicitação. Para dados dependentes do usuário, você precisa de uma estratégia adequada de servidor ou carregamento cliente.
Estado e cabeçalhos
serializeState e renderStateScript resolvem a serialização para o contexto HTML/script. escapeHtml escapa texto, não valida uma aplicação HTML arbitrária. Não injete segredos no estado de hidratação: tudo o que é publicado nessa página fica visível para seu leitor.
createNonce() produz um nonce para uma resposta. securityHeaders, applySecurityHeaders, wuSecurityMiddleware e withSecurityHeaders ajudam a instalar CSP e outros cabeçalhos. Use o mesmo nonce da resposta nos estilos/scripts que devem ser executados e um novo nonce na próxima resposta; o servidor controla essa coordenação.
Referência de implementação
Exportações: src/server/index.js; render: src/server/wu-server-render.js; múltiplas renderizações: src/server/wu-server-render.js; tipos: src/server/index.d.ts; adoção do DOM: src/core/wu-sandbox.js; slot hydrate: src/core/wu-core.js; Astro: integrations/astro/WuAppSSR.astro. Testes: tests/unit/server-render.test.js, tests/unit/server-security-fuzz.test.js, tests/unit/security-headers.test.js.
Para isolar cada requisição e limitar trabalho, consulte createServerRequest, loaders privados, cancelamento e concorrência na referência SSR. Os wrappers de cabeçalhos 4.0 rejeitam um nonce fixo: use uma função por requisição ou a geração padrão.