Error Encyclopedia

Hydration Missing Siblings

This error is similar to the error NG0500, but it means Angular's hydration process expected more sibling nodes at a specific spot in the DOM and did not find them. Check the error NG0500 page for causes related to direct DOM manipulation.

A common cause: conditional content that differs between server and client

Besides direct DOM manipulation, this error often happens when content inside @if or @for renders differently on the server than it does when the client hydrates. If the server rendered a condition as true (or a list with more items), but the client sees it as false (or a shorter list) when hydration runs, the client ends up expecting fewer sibling nodes than the server actually sent. Hydration then fails while walking through the siblings.

A common way this happens is when a value comes from an input(), but instead of using that input directly (or through computed()), it gets copied into a plain signal() one time inside ngOnInit() or the constructor:

@Component({
  selector: 'app-example',
  template: `
    @if (visibleItems().length) {
      <ul>
        @for (item of visibleItems(); track item) {
          <li>{{ item }}</li>
        }
      </ul>
    }
  `,
})
export class Example {
  items = input<string[]>();

  // This only runs once. If `items()` is not ready yet at the exact
  // moment `ngOnInit` runs, this signal can end up holding a different
  // value on the server than it does on the client. That means a
  // different number of DOM nodes gets rendered on each side, which
  // causes a hydration mismatch.
  visibleItems = signal<string[]>([]);

  ngOnInit() {
    this.visibleItems.set(this.items() ?? []);
  }
}

The fix is to derive the value instead of copying it once. That way every render, including the very first one, always matches the input:

export class Example {
  items = input<string[]>();

  visibleItems = computed(() => this.items() ?? []);
}

If you also need to let users override that value locally, for example a "show more" button that reveals extra items, use linkedSignal() instead of a plain signal() set from a lifecycle hook.

Debugging the error

See the error NG0500 for tips on debugging causes related to direct DOM manipulation.

This error alone will not tell you which component caused it. The Angular DevTools browser extension can help: it can highlight the exact component where the hydration mismatch happened. Once you find it, look at how it computes any value used inside an @if or @for in its template. Check whether that value comes straight from an input() (through a template binding or computed()), or whether it gets copied into a signal inside a lifecycle hook like shown above.