Skip to content

Hydration

The server adapter writes a <template shadowrootmode="open"> inside the custom element. The browser turns that template into a shadow root while it parses the page, before it runs the component bundle.

This path requires Svelte’s default open shadow root. Do not set customElement.shadow to "none" on a server-rendered component. Svelte then mounts into the light DOM and cannot adopt the declarative shadow root.

After the component bundle loads, Svelte’s generated element class calls attachShadow(). The browser would clear and return a matching declarative shadow root. svebcomponents intercepts that call through Svelte’s public customElement.extend option and returns the existing root before the browser clears it. The extension then calls Svelte’s hydrate() function with the server-rendered root.

Svelte adopts the existing shadow DOM if the client and server output match. The server-rendered nodes stay in place throughout hydration.

Before Svelte hydrates, the svebcomponents extension collects:

  • attributes on the custom element
  • rich properties that the server renderer serializes into the shadow root
  • properties assigned before the element upgraded

Property updates and configured attribute reflection continue after hydration. The extension removes the serialized property payload from the shadow root.

The element uses Svelte’s mount path when it cannot hydrate:

SituationResult
The server did not provide a declarative shadow rootSvelte mounts the component in an empty root.
The component declares a <slot>svebcomponents clears the server content, then Svelte mounts the component.
The element reconnects after Svelte tore it downSvelte mounts a new component instance.
Client and server structure differSvelte reports the mismatch and mounts fresh output.

Fallbacks that start with server content replace it.

Svelte 5 compiles <slot> in custom-element mode through a legacy transform that does not support this hydration path. Slotted components still receive server-rendered shadow DOM, then re-render when the element upgrades. Dev mode logs the fallback.

Events dispatched through $host() fire on the custom element after hydration. Set bubbles: true on the event when it must bubble through ancestor elements. The hydration host does not forward events created with createEventDispatcher.

The hydration host does not expose component functions as methods on the custom element.

Set hydratable: false for a client-mounting package:

import { defineConfig } from "@svebcomponents/build";
export default defineConfig({
hydratable: false,
});

To supply your own element extension, set extend in <svelte:options customElement={{ ... }}>. Auto-options detects that extension and skips its hydration injection.