Le prérendu consiste à envoyer le balisage d'un élément personnalisé (y compris son arborescence shadow) depuis le serveur, de sorte que le composant soit stylisé et structuré avant l'exécution de tout code JavaScript. La principale primitive de plate-forme pour cela est le Shadow DOM déclaratif (DSD) : l'analyseur associe la racine fantôme à partir du code HTML uniquement, et l'élément est ensuite mis à niveau ("hydraté") par sa classe si une classe est enregistrée. Utilisez ce modèle pour le rendu côté serveur, le streaming et pour éviter l'affichage d'un contenu sans style (FOUC).
Créer une racine de shadow DOM déclarative
Imbriquez un <template> avec l'attribut shadowrootmode en tant que premier enfant de l'hôte. L'analyseur associe son contenu en tant que racine fantôme et supprime le modèle. Aucun script n'est nécessaire.
<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>
Hydratation
Lorsque la classe correspondante est enregistrée, le navigateur met à niveau l'hôte. Comme la racine fantôme existe déjà, le constructeur ne doit pas en créer une deuxième à l'aveugle :
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'));
}
}
Définissez shadowrootmode="open" (et non closed) dans presque tous les cas, afin que les scripts et les outils puissent atteindre this.shadowRoot pour l'hydratation. Pour sérialiser une racine fantôme existante en HTML sur le serveur ou dans les tests, utilisez getHTML({ serializableShadowRoots: true }) et associez la racine avec serializable: true.
Éviter le FOUC
Le DSD ne s'affiche correctement lors du premier rendu que si les styles sont inclus dans le modèle déclaratif. Pour préserver cela :
- Placez le CSS du composant dans le
<template shadowrootmode>(une balise<style>ou un<link>), et non dans une feuille distincte chargée par un script. - Associez-le à
my-card:not(:defined) { … }uniquement pour les styles qui doivent attendre le JS, généralement des règles de protection au niveau de la page, comme un fondu sur les instances affichées par le client qui ne sont pas prises en compte par le SSR. Ils se trouvent dans la feuille de style light-DOM (page), car la racine shadow n'y existe pas encore et les sélecteurs:hostne s'appliquent pas de l'extérieur. Les styles structurels du modèle DSD n'ont pas besoin d'une telle protection. - Ne pas afficher à nouveau l'arborescence shadow lors de l'hydratation. Traiter le balisage du serveur comme faisant autorité et lui associer un comportement.
Stratégies de remplacement
Dans les moteurs qui n'analysent pas shadowrootmode, le modèle est inerte et son contenu n'est pas associé en tant que racine fantôme. Détectez ce problème (Object.hasOwn(HTMLTemplateElement.prototype, 'shadowRootMode')) et, s'il est absent, associez impérativement la racine shadow à partir du même balisage lors de la mise à niveau afin que le composant s'affiche toujours.