이 오류는 NG0500 오류와 비슷하지만, Angular의 하이드레이션 과정에서 DOM의 특정 위치에 더 많은 형제 노드가 있을 것으로 예상했으나 찾지 못했다는 뜻입니다. 직접 DOM 조작과 관련된 원인은 NG0500 오류 페이지를 참고하세요.
흔한 원인: 서버와 클라이언트에서 달라지는 조건부 콘텐츠
직접 DOM 조작 외에도 @if나 @for 안의 콘텐츠가 서버에서 렌더링될 때와 클라이언트에서 하이드레이션될 때 다르면 이 오류가 흔히 발생합니다. 서버에서는 조건이 true이거나 목록 항목이 더 많았지만 하이드레이션 실행 시 클라이언트에서는 false이거나 목록이 더 짧다면, 클라이언트는 서버가 실제로 보낸 것보다 적은 형제 노드를 예상하게 됩니다. 그러면 형제 노드를 순회하는 중에 하이드레이션이 실패합니다.
input()의 값을 직접 사용하거나 computed()로 계산하지 않고 ngOnInit()이나 생성자에서 일반 signal()에 한 번만 복사할 때 이런 문제가 흔히 발생합니다.
@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[]>();
// 이 코드는 한 번만 실행됩니다. `ngOnInit`이 실행되는 시점에
// `items()`가 아직 준비되지 않았다면 이 시그널은 서버와 클라이언트에서
// 서로 다른 값을 가질 수 있습니다. 그러면 양쪽에서
// 렌더링하는 DOM 노드 수가 달라져
// 하이드레이션 불일치가 발생합니다.
visibleItems = signal<string[]>([]);
ngOnInit() {
this.visibleItems.set(this.items() ?? []);
}
}
값을 한 번 복사하는 대신 입력값에서 계산하도록 바꾸면 해결할 수 있습니다. 그러면 첫 렌더링을 포함한 모든 렌더링에서 값이 항상 입력값과 일치합니다.
export class Example {
items = input<string[]>();
visibleItems = computed(() => this.items() ?? []);
}
추가 항목을 보여주는 “더 보기” 버튼처럼 사용자가 해당 값을 로컬에서 덮어쓸 수 있어야 한다면, 라이프사이클 훅에서 값을 설정하는 일반 signal() 대신 linkedSignal()을 사용하세요.
오류 디버깅
직접 DOM 조작과 관련된 원인을 디버깅하는 방법은 NG0500 오류를 참고하세요.
이 오류만으로는 어떤 컴포넌트가 원인인지 알 수 없습니다. Angular DevTools 브라우저 확장 프로그램은 하이드레이션 불일치가 발생한 정확한 컴포넌트를 강조 표시하므로 도움이 됩니다. 컴포넌트를 찾았다면 템플릿의 @if나 @for 안에서 사용하는 값을 어떻게 계산하는지 살펴보세요. 해당 값이 템플릿 바인딩이나 computed()를 통해 input()에서 바로 오는지, 아니면 앞선 예제처럼 라이프사이클 훅에서 시그널에 복사되는지 확인하세요.