Renderização no servidor
Renderize componentes em HTML no servidor e hidrate no navegador, com o mesmo código dos dois lados.
Configuração
npm install @velozjs/dom @velozjs/ssr
npm install -D vite vite-plugin-velozCom o vite-plugin-veloz, seus imports de @velozjs/dom apontam para @velozjs/ssr durante o SSR (em dev com ssrLoadModule e em vite build --ssr), então os componentes não precisam saber onde rodam.
Entrada do servidor
import { renderToString } from '@velozjs/ssr';
import { App } from './App';
export function render(url: string) {
return renderToString(() => <App url={url} />); // { html, css }
}HTML base
<!doctype html>
<html lang="pt-BR">
<head>
<!--ssr-css-->
</head>
<body>
<div id="app"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.tsx"></script>
</body>
</html>const { html, css } = render(req.url);
res.end(
template
.replace('<!--ssr-css-->', `<style data-veloz-css>${css}</style>`)
.replace('<!--ssr-outlet-->', html)
);Entrada do cliente
import { hydrate } from '@velozjs/dom';
import { App } from './App';
hydrate(() => <App url={location.pathname} />, document.getElementById('app')!);Dados assíncronos
O renderToString é síncrono: recursos que ainda estão carregando renderizam o fallback do Suspense. Para enviar a página com os dados, use o renderToStringAsync:
import { renderToStringAsync } from '@velozjs/ssr';
const { html, css, script } = await renderToStringAsync(() => <App url={req.url} />, {
timeout: 5000,
});
res.end(
template
.replace('<!--ssr-css-->', `<style data-veloz-css>${css}</style>`)
.replace('<!--ssr-outlet-->', html + script) // o script precisa rodar antes da entrada do cliente
);Ele renderiza, espera as buscas pendentes e renderiza de novo com os valores, repetindo quando uma requisição depende de outra. O script define os valores para o hydrate(), que inicia cada recurso com eles em vez de buscar.
| Opção | Padrão | |
|---|---|---|
timeout | nenhum | rejeita se os dados demorarem mais (ms) |
maxPasses | 10 | limite para cadeias de requisições dependentes |
nonce | nenhum | nonce de CSP para o script de dados |
Os valores precisam ser serializáveis em JSON; cargas que falharam são enviadas com a mensagem de erro. O JSON é escapado para não conseguir sair do <script>.
Como a hidratação funciona
- O servidor dá a cada elemento uma chave
data-hkna ordem de criação e envolve cada região dinâmica em comentários<!--[-->…<!--]-->. - O
hydrate()indexa as chaves dentro do container uma vez e cria a mesma árvore: os elementos são reaproveitados em vez de criados, eventos e effects são ligados e as chaves são removidas. - As regiões dinâmicas se ligam aos marcadores, então o texto estático ao lado de texto dinâmico fica como o servidor enviou.
- A hidratação é limitada ao container, então várias raízes independentes na mesma página funcionam. Este site faz isso: cada parte interativa é uma raiz pequena.
O que muda no servidor
createEffecteonMountnão rodam (servem para efeitos no navegador), então o código dentro deles pode usarwindowedocumentà vontade. Memos e recursos rodam. Handlers de evento não são impressos.innerHTMLnão é impresso (não há sanitizador de DOM no servidor); o cliente o define, sanitizado, na hidratação.- O
Portalnão renderiza nada; o conteúdo é criado no cliente. - O
renderToStream()devolve a mesma saída como stream de bytes, que você passa paranew Response().
Sites estáticos
A mesma API pré-renderiza páginas no build: renderize cada rota com renderToString (ou renderToStringAsync) e grave o HTML no disco. Este site é feito assim, com só as partes interativas hidratadas.