Hydration and SSR migration
Use this add-on path after the core migration if your v2 application
server-rendered FAST components, used @microsoft/fast-ssr, emitted FAST
hydration markers, imported hydration side-effect helpers, or inspected
hydration controller APIs. Complete the
core migration first, then update the SSR and
hydration pipeline so the server output and browser runtime agree.
What changed
FAST Element 3.x keeps the client runtime browser-only and makes hydration
explicit. The server can still emit HTML and Declarative Shadow DOM, but the
browser Window defines custom elements, runs enableHydration(), and attaches
bindings to the existing DOM.
Key migration points:
- Hydration is opt-in with
enableHydration(). HydratableElementControllerand hydration side-effect imports are removed.needs-hydrationanddefer-hydrationare no longer required in rendered markup.- Hydration callbacks are replaced by hydration promises.
- SSR hydration marker syntax changed; server and client FAST versions must match.
Install hydration explicitly
The HydratableElementController class has been removed. Its functionality is
now built into ElementController through automatic prerendered content
detection. When a component connects with an existing shadow root from SSR or
Declarative Shadow DOM, FAST can hydrate the existing DOM if hydration was
enabled before the element connects.
// Before
import { HydratableElementController } from "@microsoft/fast-element";
HydratableElementController.install();
// After
import { enableHydration } from "@microsoft/fast-element/hydration.js";
enableHydration();
Remove these side-effect imports:
| Removed import | Replacement |
|---|---|
@microsoft/fast-element/install-element-hydration.js |
No side-effect import. Call enableHydration() before FAST elements connect. |
@microsoft/fast-element/install-hydration.js |
No side-effect import. Call enableHydration() before FAST elements connect. |
@microsoft/fast-element/install-hydratable-view-templates.js |
No side-effect import. enableHydration() installs hydratable template support. |
Call enableHydration() before defining or connecting FAST elements.
import { enableHydration } from "@microsoft/fast-element/hydration.js";
const hydration = enableHydration();
await import("./components.js");
await hydration.whenHydrated();
If a prerendered element connects before enableHydration() runs, the existing
shadow root is treated as client-render fallback: isPrerendered resolves
true, isHydrated resolves false, and FAST replaces the prerendered content
with a client-rendered view. Calling enableHydration() later does not hydrate
elements that already completed their first render.
Remove legacy hydration attributes
needs-hydration and defer-hydration are no longer needed in server-rendered
markup. Existing shadow root detection determines whether the element was
prerendered.
<!-- Before -->
<my-component defer-hydration needs-hydration text="Hello">
<template shadowrootmode="open">...</template>
</my-component>
<!-- After -->
<my-component text="Hello">
<template shadowrootmode="open">...</template>
</my-component>
The root deferHydrationAttribute export has also moved. Most applications can
remove defer-hydration entirely. If compatibility code still needs the legacy
attribute string, import it from the hydration path export.
import { deferHydrationAttribute } from "@microsoft/fast-element/hydration.js";
Replace hydration callbacks with promises
HydrationControllerCallbacks has been replaced by the controller returned from
enableHydration().
// Before
import { HydratableElementController } from "@microsoft/fast-element";
HydratableElementController.config({
/* hydration callbacks */
});
// After
import { enableHydration } from "@microsoft/fast-element/hydration.js";
const hydration = enableHydration();
await hydration.whenHydrated();
Use hydration.whenHydrated("my-element") when application code needs to wait
for hydration work associated with a specific tag name. Use
hydration.whenHydrated() for the active global hydration batch. When
stopHydration: StopHydration.never is configured, the global promise
intentionally remains pending because hydration never reaches a global
completion point.
Use the split prerender and hydration state
The isPrerendered property on ElementController and ViewController now
resolves true when the element had Declarative Shadow DOM at connect time,
regardless of whether hydration ran. The isHydrated property resolves true
only when hydration actually ran successfully.
async connectedCallback() {
super.connectedCallback();
const prerendered = await this.$fastController.isPrerendered;
const hydrated = await this.$fastController.isHydrated;
if (prerendered && !hydrated) {
// The element had SSR content but hydration was not enabled in time.
}
}
Replace element.$fastController instanceof HydratableElementController checks
with the isPrerendered and isHydrated promises.
Update SSR output markers
The SSR hydration markers have been simplified from verbose, index-embedded comments to compact sequential markers. This is a breaking change to the SSR output format. SSR and client versions must match.
| Old format | New format |
|---|---|
fe-b$$start$$<index>$$<scopeId>$$fe-b |
fe:b |
fe-b$$end$$<index>$$<scopeId>$$fe-b |
fe:/b |
fe-repeat$$start$$<itemIndex>$$fe-repeat |
fe:r |
fe-repeat$$end$$<itemIndex>$$fe-repeat |
fe:/r |
fe-eb$$start$$<elementId>$$fe-eb |
fe:e |
fe-eb$$end$$<elementId>$$fe-eb |
fe:/e |
data-fe-b="0 1 2" / data-fe-b-0 / data-fe-c-0-3 |
data-fe="N" |
The HydrationMarkup API methods have also changed.
| Removed or changed API | Replacement |
|---|---|
HydrationMarkup.contentBindingStartMarker(index, scopeId) |
HydrationMarkup.contentBindingStartMarker() |
HydrationMarkup.contentBindingEndMarker(index, scopeId) |
HydrationMarkup.contentBindingEndMarker() |
HydrationMarkup.parseAttributeBinding(element) |
HydrationMarkup.parseAttributeBindingCount(element) |
HydrationMarkup.parseRepeatStartMarker(data) |
HydrationMarkup.isRepeatViewStartMarker(data) |
HydrationMarkup.parseRepeatEndMarker(data) |
HydrationMarkup.isRepeatViewEndMarker(data) |
HydrationMarkup.parseElementBoundaryStartMarker(content) |
HydrationMarkup.isElementBoundaryStartMarker(node) |
HydrationMarkup.parseElementBoundaryEndMarker(content) |
HydrationMarkup.isElementBoundaryEndMarker(node) |
If you used @microsoft/fast-ssr or custom SSR tooling, update the renderer to emit the
new marker format before loading the v3 client. If you use @microsoft/fast-build,
upgrade it with @microsoft/fast-element and rebuild the output.
Keep renderer and client versions in sync
Hydration succeeds when the HTML produced by the server matches the template and data that the client runtime sees during the element's first render. The renderer and client both rely on the same depth-first binding order and marker syntax. Deploy server-rendered output and client bundles together so old marker output is not hydrated by the v3 client, and the v3 marker output is not loaded with an older client.
Do not minify or sanitize away FAST comments or data-fe attributes before the
client loads. A missing fe:/b marker, an invalid data-fe count, or a changed
DOM shape means the hydration walker cannot target the compiled bindings.
Troubleshooting hydration
If hydration does not run, FAST renders client-side. If hydration starts and
detects a recoverable mismatch, such as empty render() view boundaries or a
repeat() whose SSR item count disagrees with the client array, it reconciles by
falling back to the client view or by creating/removing the affected items. If
the mismatch cannot be reconciled, FAST throws either HydrationBindingError or
HydrationTargetElementError.
Pass hydrationDebugger() through enableHydration() to replace the default
one-line mismatch message with an "Expected / Received" report and structured
expected and received fields.
import {
enableHydration,
hydrationDebugger,
} from "@microsoft/fast-element/hydration.js";
enableHydration({ debugger: hydrationDebugger() });
With the debugger installed, an unrecoverable mismatch produces output such as:
Hydration mismatch in .
Expected: with content binding
Received: server
Hydration exports added in 3.x
| Export | Package | Description |
|---|---|---|
enableHydration() |
@microsoft/fast-element/hydration.js |
Enables hydration support for FAST elements. |
deferHydrationAttribute |
@microsoft/fast-element/hydration.js |
Legacy defer-hydration attribute string for compatibility code. |
HydrationTracker |
@microsoft/fast-element/hydration.js |
Standalone hydration lifecycle tracker class. |
HydrationOptions |
@microsoft/fast-element/hydration.js |
Type for hydration configuration options. |
Migration checklist
- Remove
HydratableElementController.install()andHydratableElementController.config()usage. - Remove hydration side-effect imports.
- Call
enableHydration()before FAST elements connect. - Replace hydration callbacks with
whenHydrated()waits. - Replace prerender checks with
isPrerenderedandisHydrated. - Remove
needs-hydrationanddefer-hydrationfrom SSR markup unless compatibility code still explicitly needs the legacy attribute string. - Update
@microsoft/fast-ssr,@microsoft/fast-build, or custom SSR output to the v3 hydration marker format. - Deploy matching renderer and client versions.