このエラーは、error NG0500と似ていますが、Angularのハイドレーションプロセスが特定の場所でDOM構造内に存在するはずの兄弟要素が不足していることを示しています。DOMの直接操作に関する原因については、error NG0500ページを確認してください。
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.
エラーのデバッグ
DOMの直接操作に起因する原因のデバッグのヒントについては、error NG0500を参照してください。
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.