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 renderização do servidor 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.
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.
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.