prerender
prerender
renderiza uma árvore React em uma string HTML estática usando um Web Stream.
const {prelude} = await prerender(reactNode, options?)
Referência
prerender(reactNode, options?)
Chame prerender
para renderizar seu app em HTML estático.
import { prerender } from 'react-dom/static';
async function handler(request) {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}
No cliente, chame hydrateRoot
para tornar o HTML gerado pelo servidor interativo.
Parâmetros
-
reactNode
: Um nó React que você quer renderizar em HTML. Por exemplo, um nó JSX como<App />
. Espera-se que ele represente o documento inteiro, então o componente App deve renderizar a tag<html>
. -
opcional
options
: Um objeto com opções de geração estática.- opcional
bootstrapScriptContent
: Se especificado, esta string será colocada em uma tag<script>
inline. - opcional
bootstrapScripts
: Uma array de URLs de string para as tags<script>
a serem emitidas na página. Use isso para incluir o<script>
que chamahydrateRoot
. Omita-o se você não quiser executar o React no cliente. - opcional
bootstrapModules
: ComobootstrapScripts
, mas emite<script type="module">
em vez disso. - opcional
identifierPrefix
: Um prefixo de string que o React usa para IDs gerados poruseId
. Útil para evitar conflitos ao usar múltiplos roots na mesma página. Deve ser o mesmo prefixo do que aquele passado parahydrateRoot
. - opcional
namespaceURI
: Uma string com a raiz URI do namespace para o fluxo. O padrão é HTML comum. Passe'http://www.w3.org/2000/svg'
para SVG ou'http://www.w3.org/1998/Math/MathML'
para MathML. - opcional
onError
: Um retorno de chamada que é disparado sempre que há um erro de servidor, seja ele recuperável ou não. Por padrão, isso chama apenasconsole.error
. Se você substituí-lo para registrar relatórios de falhas, certifique-se de ainda chamarconsole.error
. Você também pode usá-lo para ajustar o código de status antes da emissão do shell. - opcional
progressiveChunkSize
: O número de bytes em um bloco. Saiba mais sobre a heurística padrão. - opcional
signal
: Um sinal de aborto que permite abortar a pré-renderização e renderizar o restante no cliente.
- opcional
Retorna
prerender
retorna uma Promise:
- Se a renderização for bem-sucedida, a Promise resolverá para um objeto contendo:
prelude
: um Web Stream de HTML. Você pode usar este stream para enviar uma resposta em chunks, ou você pode ler todo o stream em uma string.
- Se a renderização falhar, a Promise será rejeitada. Use isso para gerar um shell de fallback.
Ressalvas
nonce
não é uma opção disponível ao fazer pré-renderização. Nonces devem ser únicos por requisição e se você usar nonces para proteger sua aplicação com CSP, seria inadequado e inseguro incluir o valor do nonce na própria pré-renderização.
Uso
Renderizando uma árvore React em um stream de HTML estático
Chame prerender
para renderizar sua árvore React em HTML estático em um Readable Web Stream::
import { prerender } from 'react-dom/static';
async function handler(request) {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}
Junto com o componente root, você precisa fornecer uma lista de caminhos de <script>
de bootstrap. Seu componente root deve retornar todo o documento incluindo a tag <html>
root.
Por exemplo, pode se parecer com isso:
export default function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}
O React vai injetar o doctype e suas tags de <script>
de bootstrap no stream HTML resultante:
<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<script src="/main.js" async=""></script>
No cliente, seu script de bootstrap deve hidratar todo o document
com uma chamada para hydrateRoot
:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);
Isso vai anexar event listeners ao HTML estático gerado pelo servidor e torná-lo interativo.
Deep Dive
As URLs dos assets finais (como arquivos JavaScript e CSS) são frequentemente hasheadas após a build. Por exemplo, em vez de styles.css
você pode acabar com styles.123456.css
. Hashear nomes de arquivos de assets estáticos garante que cada build distinto do mesmo asset terá um nome de arquivo diferente. Isso é útil porque permite que você habilite o cache de longo prazo com segurança para ativos estáticos: um arquivo com um determinado nome nunca mudaria o conteúdo.
No entanto, se você não souber as URLs dos assets até depois da build, não haverá como colocá-los no código-fonte. Por exemplo, codificar "/styles.css"
em JSX como antes não funcionaria. Para mantê-los fora do seu código-fonte, seu componente root pode ler os nomes reais dos arquivos de um mapa passado como uma prop:
export default function App({ assetMap }) {
return (
<html>
<head>
<title>My app</title>
<link rel="stylesheet" href={assetMap['styles.css']}></link>
</head>
...
</html>
);
}
No servidor, renderize <App assetMap={assetMap} />
e passe o seu assetMap
com as URLs dos assets:
// Você precisa obter este JSON de sua ferramenta de build, por exemplo, ler da saída da build.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};
async function handler(request) {
const {prelude} = await prerender(<App assetMap={assetMap} />, {
bootstrapScripts: [assetMap['/main.js']]
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}
Como seu servidor agora está renderizando <App assetMap={assetMap} />
, você precisa renderizá-lo com assetMap
no cliente também para evitar erros de hidratação. Você pode serializar e passar assetMap
para o cliente assim:
// Você precisa obter este JSON de sua ferramenta de build.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};
async function handler(request) {
const {prelude} = await prerender(<App assetMap={assetMap} />, {
// Cuidado: é seguro stringificar() isto porque estes dados não são gerados pelo usuário.
bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`,
bootstrapScripts: [assetMap['/main.js']],
});
return new Response(prelude, {
headers: { 'content-type': 'text/html' },
});
}
No exemplo acima, a opção bootstrapScriptContent
adiciona uma tag <script>
inline extra que define a variável global window.assetMap
no cliente. Isso permite que o código do cliente leia o mesmo assetMap
:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App assetMap={window.assetMap} />);
Tanto o cliente quanto o servidor renderizam App
com a mesma prop assetMap
, então não há erros de hidratação.
Renderizando uma árvore React em uma string de HTML estático
Chame prerender
para renderizar seu app em uma string HTML estática:
import { prerender } from 'react-dom/static';
async function renderToString() {
const {prelude} = await prerender(<App />, {
bootstrapScripts: ['/main.js']
});
const reader = prelude.getReader();
let content = '';
while (true) {
const {done, value} = await reader.read();
if (done) {
return content;
}
content += Buffer.from(value).toString('utf8');
}
}
Isso irá produzir a saída HTML inicial não interativa de seus componentes React. No cliente, você precisará chamar hydrateRoot
para hidratar aquele HTML gerado pelo servidor e torná-lo interativo.
Esperando todos os dados carregarem
prerender
espera todos os dados carregarem antes de finalizar a geração do HTML estático e resolver. Por exemplo, considere uma página de perfil que mostra uma capa, uma barra lateral com amigos e fotos e uma lista de posts:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}
Imagine que <Posts />
precisa carregar alguns dados, o que leva algum tempo. Idealmente, você gostaria de esperar até os posts terminarem para que fossem incluídos no HTML. Para fazer isso, você pode usar Suspense para suspender nos dados, e prerender
vai esperar o conteúdo suspenso acabar antes de resolver o HTML estático.
Abortando a pré-renderização
Você pode forçar a pré-renderização a “desistir” após um timeout:
async function renderToString() {
const controller = new AbortController();
setTimeout(() => {
controller.abort()
}, 10000);
try {
// o prelude conterá todo o HTML que foi pré-renderizado
// antes do controller abortar.
const {prelude} = await prerender(<App />, {
signal: controller.signal,
});
//...
Quaisquer limites de Suspense com filhos incompletos serão incluídos no prelude no estado de fallback.
Solução de problemas
Meu stream não começa até que o app inteiro seja renderizado
A resposta de prerender
espera a renderização completa de todo o aplicativo, incluindo a espera pela resolução de todos os limites de Suspense, antes de resolver. Ele é projetado para a geração estática de sites (SSG) com antecedência e não suporta o streaming de mais conteúdo conforme ele carrega.
Para transmitir conteúdo conforme ele carrega, use uma API de renderização de servidor por streaming como renderToReadableStream.