Skip to content

fix: reconcile prerendered content a content binding does not adopt#7639

Open
AKnassa wants to merge 1 commit into
microsoft:fast-element-2.xfrom
AKnassa:fix/7634-when-defer-hydration
Open

fix: reconcile prerendered content a content binding does not adopt#7639
AKnassa wants to merge 1 commit into
microsoft:fast-element-2.xfrom
AKnassa:fix/7634-when-defer-hydration

Conversation

@AKnassa

@AKnassa AKnassa commented Jul 12, 2026

Copy link
Copy Markdown

What this does

Fixes a bug where a component placed inside an <if> (a when binding) could quietly break after a page is server-rendered and then brought to life in the browser. The component would show up on screen but be completely dead — it never responded to data — and sometimes a second copy of it appeared next to the first.

Fixes #7634.

Why

If a page is rendered on the server with the <if> condition true, the server sends down the content inside it. When the browser takes over, the condition may not be true yet — typically because the data it depends on hasn't loaded. When that happened, the content the server sent was left sitting in the page with nothing looking after it. Nothing ever connected it to the data, so it stayed frozen forever, and once the data did arrive a fresh copy was added beside it.

The same component inside a <for each> (a repeat binding) always worked, because repeat tidies up server-rendered content it can't reuse. <if> didn't. That's the whole difference, and it's why the known workaround was to swap <if> for a one-item <for each>.

What changed

  • A conditional binding now tidies away server-rendered content it can't reuse, exactly the way repeat already does — so nothing is left orphaned, and no duplicate appears.
  • Added a test fixture that server-renders a defer-and-hydrate component inside an <if>, with the same component inside a <for each> alongside it as a control, so the two are held to the same standard from now on.

No public API changed.

How to see it

The new fixture is packages/fast-html/test/fixtures/when-nested-elements/.

npm run build:fixtures -w @microsoft/fast-html
npx playwright test -w @microsoft/fast-html when-nested-elements

Two of those tests fail without the change and pass with it. Full suites are green on Chromium, Firefox and WebKit: fast-element 4281 passing, fast-html 987 passing.

Notes for reviewers

A few things worth your judgement:

  • This targets fast-element-2.x, since that's where fast-html and fast-element 2.x live. main is fast-element 3.0.1 and has no fast-html package, so this scenario can't arise there.
  • The issue's own root-cause analysis doesn't hold up. The server renderer does emit boundary markers for when, and bindingViewBoundaries is populated for it — so the missing-boundary theory in the report isn't the cause. The real problem is that the content binding never reconciles a range it doesn't adopt.
  • I could not reproduce the exact symptom described (the child keeping defer-hydration/needs-hydration forever). What does reproduce, from the same root cause and the same code path, is the orphaned-plus-duplicated content fixed here. If you have a repro of the attribute-retention symptom, I'd like to check it against this.
  • The regression test lives in fast-html, not fast-element, because it needs the real server renderer and its markers. Both run in this repo's CI, so the fix is guarded — but fast-element's own suite doesn't cover it.
  • Possibly related, left alone: RenderBehavior.bind in render.ts reads the same boundaries and, if they're missing, renders nothing at all. Looks like the same family of bug, but out of scope here.

A content binding hydrates by adopting the nodes the server rendered between
its boundary markers. When it could not adopt them it left them in the DOM
owned by no view, so nothing ever bound them, and once the value resolved a
second, client-rendered copy was composed alongside them.

This happens when a `when` condition is falsy as the element hydrates but the
server rendered the branch, which is the case whenever the condition depends
on data that has not resolved yet. `repeat` is unaffected because it
reconciles its prerendered item views against the live DOM instead.

Discard the prerendered range the binding does not adopt, consuming the
boundary so a range is discarded at most once.

Adds a fast-html hydration fixture covering a `defer-and-hydrate` child
rendered inside a conditional content binding, with the equivalent repeat
binding as a control.

Fixes microsoft#7634
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@AKnassa AKnassa marked this pull request as ready for review July 12, 2026 04:40
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant