Sitelet https://github.com/angular/angular/pull/70249
Skip to content

refactor(core): show bounded paths in hydration errors - #70249

Open
splincode wants to merge 3 commits into
angular:mainfrom
splincode:splincode/fix-hydration-mismatch-location
Open

refactor(core): show bounded paths in hydration errors#70249
splincode wants to merge 3 commits into
angular:mainfrom
splincode:splincode/fix-hydration-mismatch-location

Conversation

@splincode

@splincode splincode commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

PR Checklist

PR Type

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Hydration mismatch errors describe the mismatched node and its immediate parent. When multiple branches have the same local structure, that snippet does not identify which branch in the component template contains the mismatch.

Issue Number: #56392

What is the new behavior?

Hydration mismatch errors retain the compact local expected and actual DOM snippets and add a bounded DOM path:

  • at most four real nodes are shown: the component host and the three nodes closest to the mismatch;
  • omitted middle ancestors are represented by ...;
  • the expected path follows template declaration ancestry, while the actual path follows the physical DOM to the nearest component host;
  • element, text, and comment mismatch locations are represented in the path.

The integration test uses two otherwise-identical nested branches, Account and Billing. The local snippets are the same, while the bounded path identifies the Billing branch without rendering the full ancestry tree.

The change only improves development-mode diagnostics. It does not add DOM validation, recognize specific mismatch patterns, or change when hydration reports a mismatch.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

No documentation update is needed because this change does not introduce or modify a public API.

Tested with:

pnpm bazel test //packages/platform-server/test:test

Build expected and actual mismatch descriptions through their respective ancestry so hydration errors identify the precise location without adding DOM validation.

Closes angular#56392
@angular-robot angular-robot Bot added the area: core Issues related to the framework runtime label Aug 18, 2026
@ngbot ngbot Bot added this to the Backlog milestone Aug 18, 2026
@splincode
splincode marked this pull request as ready for review August 18, 2026 06:43
@pullapprove
pullapprove Bot requested a review from atscott August 18, 2026 06:43
@JeanMeche
JeanMeche requested review from JeanMeche and removed request for atscott August 18, 2026 16:46
@JeanMeche

Copy link
Copy Markdown
Member

I'm afraid this change would bring too much noise to the error message, especially for deeply nested hydration issues.

Do you have a concrete example where such additional debug information was useful ?

Keep hydration mismatch snippets local while adding a bounded DOM path that identifies otherwise ambiguous nested branches.
@splincode splincode changed the title refactor(core): show full ancestry in hydration errors refactor(core): show bounded paths in hydration errors Aug 18, 2026
@splincode

Copy link
Copy Markdown
Contributor Author

Thanks, that is a fair concern. I pushed 60d820c69a to avoid rendering the full ancestry tree.

A concrete ambiguous case is now covered by the integration test. The template contains two otherwise-identical branches:

<section aria-label="Account">
  <div class="name"><b>Alice</b></div>
</section>
<section aria-label="Billing">
  <div class="name"><b>Bob</b></div>
</section>

If the <b> in Billing is replaced by a <span>, the compact local snippets only show:

<div class="name">
  <b>…</b>  <-- AT THIS LOCATION
</div>

and:

<div class="name">
  <span>…</span>  <-- AT THIS LOCATION
</div>

That identifies the mismatch, but not which identical branch contains it. The revised output keeps those local snippets and adds only this bounded path:

DOM path: <app> > ... > <section aria-label="Billing"> > <div class="name"> > <b>

The path displays at most four real nodes: the component host plus the three nodes closest to the mismatch. Any middle ancestry is replaced with ..., so the output size stays bounded for deeply nested templates. The test also covers non-element mismatch nodes so text or comment locations do not produce an empty path.

Would this bounded format address the noise concern?

@JeanMeche

Copy link
Copy Markdown
Member

I think the current proposition lacks in readability. Wondering if we could do something like :
But where would cap the number of parent elements we're mentionning.

DOM path:
<app>
 └─ ...
    └─ <section aria-label="Billing">
       └─ <div class="name">
          └─ <b>

Also Maybe there is something the devtools could provide, if we give it the correct info.

Render bounded hydration paths as vertical trees so deeply nested mismatch locations remain easy to scan in errors and DevTools.
@splincode

splincode commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, I updated the output to use the vertical tree format:

DOM path:
<app>
 └─ ...
    └─ <section aria-label="Billing">
       └─ <div class="name">
          └─ <b>

The cap remains four real nodes in total: the component host plus the three nodes closest to the mismatch. The ... line does not count toward that limit. Paths with four or fewer nodes are displayed without the ellipsis. This keeps the output size bounded while preserving enough local context to distinguish the otherwise-identical branches in the test.

I also checked the existing DevTools flow. Core already attaches the exact expectedNodeDetails and actualNodeDetails strings to the nearest component host via __ngDebugHydrationInfo__; the DevTools backend passes those fields through and the UI renders them in <pre>. Therefore this more readable format is automatically available in DevTools as well. Pointing DevTools at the exact non-component DOM child would require a separate core/protocol change, so I have not expanded this PR in that direction.

Does the four-node cap look reasonable to you?

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

Labels

area: core Issues related to the framework runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants