A pré-renderização significa enviar a marcação de um elemento personalizado, incluindo a árvore de sombra, do servidor para que o componente seja estilizado e estruturado antes da execução de qualquer JavaScript. A principal primitiva da plataforma para isso é o DOM de sombra declarativo (DSD): o analisador anexa a raiz de sombra apenas do HTML, e o elemento é atualizado ("hidratado") posteriormente pela classe, se houver uma registrada. Use esse padrão para renderização do lado do servidor, streaming e para evitar um flash de conteúdo sem estilo (FOUC, na sigla em inglês).
Como criar uma raiz de sombra declarativa
Aninhe um <template> com o atributo shadowrootmode como o primeiro filho do host. O analisador anexa o conteúdo como uma raiz de sombra e remove o modelo. Não é necessário nenhum script.
<my-card>
<template shadowrootmode="open">
<style>
.card { border: 1px solid; border-radius: 8px; padding: 1rem; }
</style>
<div class="card">
<slot name="header"></slot>
<div class="body"><slot></slot></div>
</div>
</template>
<!-- Light DOM children are projected into the slots above. -->
<h2 slot="header">Server-rendered title</h2>
<p>Visible and styled before JS loads.</p>
</my-card>
Tempo de hidratação
Quando a classe correspondente é registrada, o navegador faz upgrade do host. Como a raiz de sombra já existe, o construtor não pode criar uma segunda raiz sem verificar:
class MyCard extends HTMLElement {
constructor() {
super();
// Reuse the server-rendered shadow root if present; only create one if absent.
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
this.shadowRoot.append(template.content.cloneNode(true));
}
// Hydrate in place: attach listeners once against existing markup (not unguarded
// in connectedCallback, which re-runs if the element moves in the DOM), and do
// NOT re-render; that would discard the server's DOM and flash.
this.shadowRoot.getElementById('toggle')
?.addEventListener('click', () => this.toggleAttribute('open'));
}
}
Defina shadowrootmode="open" (não closed) para quase todos os casos, para que os scripts e as ferramentas possam alcançar this.shadowRoot para hidratação. Para serializar uma raiz de sombra atual de volta para HTML no servidor ou em testes, use getHTML({ serializableShadowRoots: true }) e anexe a raiz com serializable: true.
Como evitar o FOUC
O DSD só é renderizado corretamente na primeira pintura se os estilos forem enviados dentro do modelo declarativo. Para preservar isso:
- Coloque o CSS do componente dentro do
<template shadowrootmode>(uma tag<style>ou<link>), não em uma planilha separada carregada por script. - Combine com
my-card:not(:defined) { … }apenas para estilos que precisam esperar pelo JS, normalmente regras de proteção no nível da página, como um fade-out em instâncias renderizadas pelo cliente que não passam pela SSR. Eles ficam na folha de estilo do light-DOM (página), já que a raiz de sombra ainda não existe lá e os seletores:hostnão se aplicam de fora. Os estilos estruturais dentro do modelo de DSD não precisam dessa proteção. - Não renderize novamente a árvore de sombra na hidratação. Trate a marcação do servidor como autoritativa e anexe o comportamento a ela.
Estratégias de fallback
Em mecanismos que não analisam shadowrootmode, o modelo é inerte e o conteúdo não é anexado como uma raiz de sombra. Detecte isso (Object.hasOwn(HTMLTemplateElement.prototype, 'shadowRootMode')) e, se estiver ausente, anexe a raiz de sombra de forma imperativa da mesma marcação durante o upgrade para que o componente ainda seja renderizado.