prerenderToNodeStream
prerenderToNodeStream
renderiza uma árvore React em uma string HTML estática usando um Node.js Stream..
const {prelude} = await prerenderToNodeStream(reactNode, options?)
Referência
prerenderToNodeStream(reactNode, options?)
Chame prerenderToNodeStream
para renderizar seu app em HTML estático.
import { prerenderToNodeStream } from 'react-dom/static';
// A sintaxe do manipulador de rotas depende da sua framework de backend
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js'],
});
response.setHeader('Content-Type', 'text/plain');
prelude.pipe(response);
});
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>
embutida. - opcional
bootstrapScripts
: Uma array de URLs de strings 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 React no cliente de forma alguma. - 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 que foi 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
prerenderToNodeStream
retorna uma Promise:
- Se a renderização for bem-sucedida, a Promise será resolvida para um objeto contendo:
prelude
: um Node.js Stream. de HTML. Você pode usar este stream para enviar uma resposta em chunks, ou pode ler o stream inteiro 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 para um stream de HTML estático
Chame prerenderToNodeStream
para renderizar sua árvore React em HTML estático em um Node.js Stream.:
import { prerenderToNodeStream } from 'react-dom/static';
// A sintaxe do manipulador de rotas depende da sua framework de backend
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js'],
});
response.setHeader('Content-Type', 'text/plain');
prelude.pipe(response);
});
Junto com o componente raiz, você precisa fornecer uma lista de caminhos <script>
para o bootstrap. Seu componente raiz deve retornar o documento inteiro, incluindo a tag <html>
raiz.
Por exemplo, pode ter esta aparência:
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>
);
}
React irá injetar o doctype e suas tags de bootstrap <script>
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 o document
inteiro com uma chamada para hydrateRoot
:
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';
hydrateRoot(document, <App />);
Isso irá associar event listeners ao HTML estático gerado pelo servidor e torná-lo interativo.
Deep Dive
As URLs finais dos assets (como arquivos JavaScript e CSS) são frequentemente hash após a build. Por exemplo, em vez de styles.css
você pode acabar com styles.123456.css
. O hashing de 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ê ative com segurança o cache de longo prazo para assets 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 há como colocá-las no código-fonte. Por exemplo, hardcodar "/styles.css"
em JSX como antes não funcionaria. Para mantê-los fora do seu código-fonte, seu componente raiz pode ler os nomes de arquivos reais 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 seu assetMap
com as URLs dos assets:
// Você precisaria obter este JSON de suas ferramentas de build, por exemplo, lê-lo da saída da build.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
bootstrapScripts: [assetMap['/main.js']]
});
response.setHeader('Content-Type', 'text/html');
prelude.pipe(response);
});
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ê precisaria obter este JSON de suas ferramentas de build.
const assetMap = {
'styles.css': '/styles.123456.css',
'main.js': '/main.123456.js'
};
app.use('/', async (request, response) => {
const { prelude } = await prerenderToNodeStream(<App />, {
// 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']],
});
response.setHeader('Content-Type', 'text/html');
prelude.pipe(response);
});
No exemplo acima, a opção bootstrapScriptContent
adiciona uma tag <script>
embutida 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 de assetMap
, então não há erros de hidratação.
Renderizando uma árvore React em uma string de HTML estático
Chame prerenderToNodeStream
para renderizar seu app em uma string HTML estática:
import { prerenderToNodeStream } from 'react-dom/static';
async function renderToString() {
const {prelude} = await prerenderToNodeStream(<App />, {
bootstrapScripts: ['/main.js']
});
return new Promise((resolve, reject) => {
let data = '';
prelude.on('data', chunk => {
data += chunk;
});
prelude.on('end', () => resolve(data));
prelude.on('error', reject);
});
}
Isso produzirá a saída HTML inicial não interativa de seus componentes React. No cliente, você precisará chamar hydrateRoot
para hidratar o HTML gerado pelo servidor e torná-lo interativo.
Esperando todos os dados carregarem
prerenderToNodeStream
espera que todos os dados carreguem antes de finalizar a geração de 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 que os posts terminassem para que fossem incluídos no HTML. Para fazer isso, você pode usar Suspense para suspender nos dados, e prerenderToNodeStream
esperará que o conteúdo suspenso termine antes de resolver para o HTML estático.
Solução de problemas
Meu stream não começa até que todo o app seja renderizado
A resposta de prerenderToNodeStream
aguarda a renderização de todo o app, incluindo a espera para que todos os limites de Suspense sejam resolvidos, antes de resolver. Ele é projetado para geração de site estático (SSG) com antecedência e não suporta streaming de mais conteúdo conforme ele carrega.
Para stream de conteúdo conforme ele carrega, use uma API de renderização do servidor em streaming como renderToPipeableStream.