하이드레이션이 무엇인가요?
하이드레이션은 서버에서 렌더링한 애플리케이션을 클라이언트에서 복원하는 과정입니다. 서버에서 렌더링한 DOM 구조 재사용, 애플리케이션 상태 유지, 서버가 이미 조회한 애플리케이션 데이터 전송 등의 작업이 이 과정에 포함됩니다.
하이드레이션은 왜 중요한가요?
하이드레이션은 DOM 노드를 다시 만드는 추가 작업을 피해서 애플리케이션 성능을 개선합니다. 대신 Angular는 런타임에 기존 DOM 엘리먼트를 애플리케이션 구조와 맞춰 보고, 가능한 경우 DOM 노드를 재사용합니다. 이 성능 개선은 최초 입력 지연(FID), 최대 콘텐츠풀 페인트(LCP), 누적 레이아웃 이동(CLS) 감소처럼 코어 웹 바이탈(CWV) 지표로 측정할 수 있습니다. 이런 수치의 개선은 SEO 성과 등에도 영향을 줍니다.
하이드레이션을 활성화하지 않으면 서버에서 렌더링한 Angular 애플리케이션은 애플리케이션의 DOM을 파괴하고 다시 렌더링하므로, UI가 깜박이는 현상이 나타날 수 있습니다. 이 재렌더링은 LCP 같은 코어 웹 바이탈에 악영향을 주고 레이아웃 이동을 일으킬 수 있습니다. 하이드레이션을 활성화하면 기존 DOM을 재사용해서 깜박임을 방지합니다.
Angular에서 하이드레이션 활성화하기
하이드레이션은 서버 사이드 렌더링(SSR)을 사용하는 애플리케이션에서만 활성화할 수 있습니다. 먼저 Angular SSR 가이드에 따라 서버 사이드 렌더링을 활성화하세요.
Angular CLI 사용하기
애플리케이션을 생성할 때 SSR을 활성화했거나, 나중에 ng add @angular/ssr를 실행하는 등 Angular CLI로 SSR을 활성화했다면 하이드레이션을 활성화하는 코드가 애플리케이션에 이미 포함되어 있습니다.
직접 설정하기
Angular CLI로 SSR을 활성화하지 않고 직접 구성했다면, 메인 애플리케이션 컴포넌트나 모듈에서 @angular/platform-browser의 provideClientHydration을 가져와 하이드레이션을 수동으로 활성화할 수 있습니다.
그런 다음 애플리케이션을 부트스트랩하는 프로바이더 목록에 이 프로바이더를 추가하세요.
import {
bootstrapApplication,
provideClientHydration,
} from '@angular/platform-browser';
...
bootstrapApplication(App, {
providers: [provideClientHydration()]
});
NgModule을 사용한다면 루트 애플리케이션 모듈의 프로바이더 목록에 provideClientHydration을 추가하면 됩니다.
import {provideClientHydration} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
@NgModule({
declarations: [App],
exports: [App],
bootstrap: [App],
providers: [provideClientHydration()],
})
export class AppModule {}
중요: 서버에서 애플리케이션을 부트스트랩하는 데 사용하는 프로바이더에도 provideClientHydration() 호출이 포함되어 있는지 확인하세요.
ng new 명령으로 생성한 기본 프로젝트 구조에서는 서버 모듈이 루트 AppModule을 가져오므로, 이 루트 모듈에 호출을 추가하는 것으로 충분합니다.
직접 구성한 환경이라면 서버 부트스트랩 설정의 프로바이더 목록에도 provideClientHydration() 호출을 추가하세요.
하이드레이션 활성화 여부 확인하기
하이드레이션을 설정하고 서버를 시작했다면 브라우저에서 애플리케이션을 여세요.
도움말: 하이드레이션이 완전히 동작하려면 DOM을 직접 조작하는 부분을 Angular 기능으로 바꾸거나 ngSkipHydration을 사용해야 할 수 있습니다.
자세한 내용은 제약 사항, DOM 직접 조작, 특정 컴포넌트에서 하이드레이션 건너뛰기를 참고하세요.
개발 모드로 애플리케이션을 실행할 때는 브라우저의 개발자 도구에서 콘솔을 열어 하이드레이션 활성화 여부를 확인할 수 있습니다. 하이드레이션한 컴포넌트와 노드 수 등 관련 통계를 담은 메시지가 표시됩니다. Angular는 서드 파티 라이브러리의 컴포넌트를 포함해서, 페이지에 렌더링된 모든 컴포넌트를 기준으로 통계를 계산합니다.
Angular DevTools 브라우저 확장 프로그램으로 페이지의 컴포넌트별 하이드레이션 상태를 확인할 수도 있습니다. Angular DevTools에서는 페이지의 어느 부분이 하이드레이션되었는지 보여주는 오버레이를 활성화할 수 있습니다. 하이드레이션 불일치 오류가 발생하면 오류를 일으킨 컴포넌트도 강조 표시합니다.
이벤트 캡처와 재실행
서버에서 렌더링한 애플리케이션은 생성된 HTML이 로드되면 바로 브라우저에 표시됩니다. 사용자는 페이지와 상호작용할 수 있다고 생각할 수 있지만, 이벤트 리스너는 하이드레이션이 완료될 때까지 연결되지 않습니다. 이벤트 재실행(Event Replay) 기능은 하이드레이션 전에 발생한 모든 이벤트를 캡처하고 하이드레이션이 완료되면 다시 실행합니다.
이벤트 재실행은 점진적 하이드레이션과 함께 활성화됩니다.
withNoIncrementalHydration()으로 점진적 하이드레이션을 비활성화해도 withEventReplay()으로 이벤트 재실행을 활성화할 수 있습니다.
다음 예제를 참고하세요:
import {
provideClientHydration,
withEventReplay,
withNoIncrementalHydration,
bootstrapApplication,
} from '@angular/platform-browser';
bootstrapApplication(App, {
providers: [provideClientHydration(withNoIncrementalHydration(), withEventReplay())],
});
이벤트 재실행 동작 방식
이벤트 재실행은 하이드레이션 과정이 끝나기 전에 발생한 사용자 이벤트를 캡처해서 사용자 경험을 개선하는 기능입니다. 캡처한 이벤트를 나중에 재실행하므로 사용자 상호작용이 사라지지 않습니다.
이벤트 재실행은 크게 세 단계로 나뉩니다:
사용자 상호작용 캡처
하이드레이션 전에 클릭과 그 밖의 브라우저 네이티브 이벤트 등 사용자가 수행하는 모든 상호작용을 캡처하고 저장합니다.이벤트 저장
Event Contract는 앞 단계에서 기록한 모든 상호작용을 메모리에 보관해서, 나중에 재실행할 때까지 잃어버리지 않도록 합니다.이벤트 재실행
하이드레이션이 완료되면 Angular가 캡처한 이벤트를 다시 실행합니다.
이벤트 재실행은 click, mouseover, focusin 같은 브라우저 네이티브 이벤트 를 지원합니다.
이벤트 재실행을 구현하는 라이브러리인 JSAction에 대해 더 알고 싶다면 README를 참고하세요.
이 기능은 하이드레이션 전에 수행한 사용자 행동이 무시되지 않도록 해서 일관된 사용자 경험을 제공합니다.
제약 사항
하이드레이션을 활성화하면 그렇지 않을 때는 없던 몇 가지 제약이 생깁니다. 애플리케이션은 서버와 클라이언트 양쪽에서 같은 DOM 구조를 생성해야 합니다. 하이드레이션 과정은 두 환경의 DOM 트리 구조가 같다고 가정합니다. 여기에는 서버에서 렌더링할 때 Angular가 생성하는 공백과 주석 노드도 포함됩니다. 서버 사이드 렌더링으로 생성한 HTML에는 이 공백과 노드가 그대로 있어야 합니다.
중요: 서버 사이드 렌더링으로 생성한 HTML은 서버에서 클라이언트로 전달되는 동안 변경되면 안 됩니다.
서버와 클라이언트의 DOM 트리 구조가 다르면 하이드레이션 과정에서 예상 구조와 실제 DOM 구조를 맞추는 데 문제가 생깁니다. 네이티브 DOM API로 DOM을 직접 조작하는 컴포넌트가 가장 흔한 원인입니다.
DOM 직접 조작
네이티브 DOM API나 innerHTML, outerHTML로 DOM을 조작하는 컴포넌트가 있으면 하이드레이션 과정에서 오류가 발생합니다.
document에 접근하거나, 특정 엘리먼트를 조회하거나, appendChild로 노드를 추가하는 경우 등이 문제가 될 수 있습니다.
DOM 노드를 분리해서 다른 위치로 옮기는 경우에도 오류가 발생합니다.
Angular가 이런 DOM 변경을 알지 못하며 하이드레이션 과정에서 처리할 수 없기 때문입니다. Angular는 특정 구조를 예상하지만 하이드레이션하려고 할 때 다른 구조를 만나게 됩니다. 이 불일치로 인해 하이드레이션이 실패하고 DOM 불일치 오류가 발생합니다(아래 참고).
이런 DOM 조작을 피하도록 컴포넌트를 리팩토링하는 것이 가장 좋습니다.
가능하다면 Angular API로 작업을 처리하세요.
당장 리팩토링할 수 없다면 하이드레이션과 호환되는 방식으로 고칠 때까지 ngSkipHydration 어트리뷰트를 사용하세요(아래 설명).
유효한 HTML 구조
컴포넌트 템플릿의 HTML 구조가 유효하지 않으면 하이드레이션 중 DOM 불일치 오류가 발생할 수 있습니다.
흔히 발생하는 예는 다음과 같습니다.
<tbody>가 없는<table><p>안의<div>- 다른
<a>안의<a>
HTML이 유효한지 확실하지 않다면 문법 검사기로 확인할 수 있습니다.
참고: HTML 표준에서는 테이블 안의 <tbody> 엘리먼트를 필수로 요구하지 않지만, 최신 브라우저는 선언되지 않은 <tbody>를 자동으로 만듭니다.
이 차이로 인한 하이드레이션 오류를 피하려면 테이블에 항상 <tbody> 엘리먼트를 명시적으로 선언하세요.
공백 보존 설정
하이드레이션 기능을 사용할 때는 preserveWhitespaces를 기본값인 false로 사용하는 것을 권장합니다.
tsconfig에 이 설정이 없다면 값은 false이므로 변경할 필요가 없습니다.
tsconfig에 preserveWhitespaces: true를 추가해서 공백 보존을 활성화하면 하이드레이션 문제가 발생할 수 있습니다.
이 설정은 아직 완전히 지원되지 않습니다.
도움말: 서버 빌드의 tsconfig.server.json과 브라우저 빌드의 tsconfig.app.json에서 이 설정이 일치하는지 확인하세요.
값이 다르면 하이드레이션이 제대로 동작하지 않습니다.
tsconfig에 이 설정을 지정하려면 tsconfig.app.json에만 지정하는 것을 권장합니다.
기본적으로 tsconfig.server.json은 이 파일의 설정을 상속합니다.
커스텀 또는 Noop Zone.js는 아직 지원되지 않습니다
하이드레이션은 애플리케이션이 안정 상태에 도달했을 때 Zone.js가 보내는 신호에 의존합니다. 이 신호를 받으면 Angular는 서버에서 직렬화를 시작하거나, 클라이언트에서 하이드레이션 후 정리 작업을 수행해서 연결되지 않은 DOM 노드를 제거합니다.
커스텀 또는 "noop" Zone.js 구현을 제공하면 "stable" 이벤트의 시점이 달라져 직렬화나 정리 작업이 너무 이르거나 늦게 실행될 수 있습니다.
이 구성은 아직 완전히 지원되지 않으며, 커스텀 Zone.js 구현에서 onStable 이벤트의 시점을 조정해야 할 수 있습니다.
오류
노드 불일치부터 잘못된 호스트 노드에 ngSkipHydration을 사용한 경우까지 여러 하이드레이션 오류가 발생할 수 있습니다.
가장 흔한 오류는 네이티브 API로 DOM을 직접 조작한 결과, 서버에서 렌더링한 예상 DOM 트리 구조를 클라이언트에서 찾거나 일치시키지 못하는 경우입니다.
이와 같은 오류가 발생할 수 있는 또 다른 경우는 앞서 유효한 HTML 구조 절에서 설명했습니다.
템플릿의 HTML 구조가 유효한지 확인하면 이 오류를 피할 수 있습니다.
하이드레이션 관련 오류의 전체 설명은 오류 레퍼런스 가이드를 참고하세요.
특정 컴포넌트에서 하이드레이션 건너뛰기
일부 컴포넌트는 DOM 직접 조작처럼 앞서 설명한 문제 때문에 하이드레이션을 활성화하면 제대로 동작하지 않을 수 있습니다.
임시 해결책으로 컴포넌트 태그에 ngSkipHydration 어트리뷰트를 추가해서 컴포넌트 전체의 하이드레이션을 건너뛸 수 있습니다.
<app-example ngSkipHydration />
또는 ngSkipHydration을 호스트 바인딩으로 설정할 수 있습니다.
@Component({
...
host: {ngSkipHydration: 'true'},
})
class ExampleComponent {}
ngSkipHydration 어트리뷰트는 Angular가 해당 컴포넌트 전체와 자식의 하이드레이션을 건너뛰도록 합니다.
이 어트리뷰트를 사용하면 컴포넌트는 하이드레이션이 활성화되지 않은 것처럼 동작하므로, 기존 내용을 파괴하고 다시 렌더링합니다.
도움말: 렌더링 문제는 해결되지만 해당 컴포넌트와 자식은 하이드레이션의 이점을 얻을 수 없습니다. 하이드레이션을 건너뛰는 표시를 제거하려면 DOM 직접 조작처럼 하이드레이션을 방해하는 패턴을 피하도록 컴포넌트 구현을 수정해야 합니다.
ngSkipHydration 어트리뷰트는 컴포넌트 호스트 노드에만 사용할 수 있습니다.
다른 노드에 추가하면 Angular가 오류를 발생시킵니다.
루트 애플리케이션 컴포넌트에 ngSkipHydration 어트리뷰트를 추가하면 사실상 애플리케이션 전체의 하이드레이션이 비활성화됩니다.
이 어트리뷰트는 신중하게 사용하세요.
다른 방법이 없을 때 사용하는 임시 해결책이며, 하이드레이션을 방해하는 컴포넌트는 수정해야 할 버그로 봐야 합니다.
하이드레이션 시점과 애플리케이션 안정 상태
애플리케이션의 안정 상태는 하이드레이션 과정에서 중요한 역할을 합니다. 하이드레이션과 그 이후의 처리는 애플리케이션이 안정 상태를 알린 뒤에만 실행됩니다. 타임아웃과 인터벌, 완료되지 않은 Promise, 대기 중인 마이크로태스크 등 여러 요인으로 안정 상태에 도달하는 시점이 늦어질 수 있습니다. 이런 경우 10초가 지나도 애플리케이션이 안정 상태에 도달하지 않았음을 나타내는 Application remains unstable 오류가 발생할 수 있습니다. 애플리케이션이 바로 하이드레이션되지 않는다면 안정 상태에 영향을 주는 요인을 확인하고, 이런 지연을 피하도록 리팩토링하세요.
애플리케이션 안정 상태 디버깅하기
provideStabilityDebugging 유틸리티는 애플리케이션이 안정 상태에 도달하지 못하는 이유를 찾는 데 도움이 됩니다.
provideClientHydration을 사용하면 개발 모드에서 이 유틸리티가 기본으로 제공됩니다.
프로덕션 번들에서 사용하거나 하이드레이션 없이 SSR을 사용하는 경우 등에는 애플리케이션 프로바이더에 직접 추가할 수도 있습니다.
애플리케이션이 예상보다 오래 걸려 안정 상태에 도달하면 콘솔에 정보를 기록합니다.
import {provideStabilityDebugging} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import 'zone.js/plugins/task-tracking'; // `provideZoneChangeDetection`과 함께 Zone.js를 사용하는 경우 추가합니다.
bootstrapApplication(App, {
providers: [provideStabilityDebugging()],
});
이 유틸리티를 활성화하면 대기 중인 작업(PendingTasks)이 콘솔에 기록됩니다.
애플리케이션이 Zone.js를 사용한다면 zone.js/plugins/task-tracking을 가져와 Angular Zone이 안정 상태에 도달하지 못하게 하는 매크로태스크도 확인할 수 있습니다.
이 플러그인은 매크로태스크가 생성된 시점의 스택 트레이스를 제공하므로 지연 원인을 찾는 데 도움이 됩니다.
중요: Angular는 프로덕션 번들에서 zone.js 작업 추적 플러그인이나 이 유틸리티를 제거하지 않습니다. 최적화된 프로덕션 빌드를 점검하는 경우를 포함해, 개발 중 안정 상태 문제를 일시적으로 디버깅하는 용도로만 사용하세요.
국제화(I18N)
도움말: 기본적으로 Angular는 i18n 블록을 사용하는 컴포넌트의 하이드레이션을 건너뛰고 처음부터 다시 렌더링합니다.
i18n 블록의 하이드레이션을 활성화하려면 provideClientHydration 호출에 withI18nSupport를 추가하세요.
import {
bootstrapApplication,
provideClientHydration,
withI18nSupport,
} from '@angular/platform-browser';
...
bootstrapApplication(App, {
providers: [provideClientHydration(withI18nSupport())]
});
서버와 클라이언트에서 일관되게 렌더링하기
Angular의 isPlatformBrowser 함수와 @if 블록을 함께 사용하는 방식처럼, 서버 사이드 렌더링과 클라이언트 사이드 렌더링에서 서로 다른 콘텐츠를 표시하는 @if 블록이나 조건문은 피하세요.
이런 렌더링 차이는 레이아웃 이동을 일으켜 최종 사용자 경험과 코어 웹 바이탈에 악영향을 줍니다.
DOM을 조작하는 서드 파티 라이브러리
렌더링을 위해 DOM 조작에 의존하는 서드 파티 라이브러리가 여럿 있습니다.
D3 차트가 대표적인 예입니다.
이런 라이브러리는 하이드레이션이 없을 때는 동작하지만, 하이드레이션을 활성화하면 DOM 불일치 오류를 일으킬 수 있습니다.
현재로서는 이런 라이브러리를 사용하다 DOM 불일치 오류가 발생하면 해당 라이브러리로 렌더링하는 컴포넌트에 ngSkipHydration 어트리뷰트를 추가할 수 있습니다.
DOM을 조작하는 서드 파티 스크립트
광고 추적기나 분석 도구 같은 많은 서드 파티 스크립트는 하이드레이션이 실행되기 전에 DOM을 수정합니다.
그러면 페이지가 Angular에서 예상하는 구조와 달라지므로 하이드레이션 오류가 발생할 수 있습니다.
가능하다면 이런 스크립트는 하이드레이션 이후로 실행을 미루세요.
AfterNextRender를 사용해서 하이드레이션 후 처리가 끝날 때까지 스크립트 실행을 미루는 방법도 고려할 수 있습니다.
CDN과 브라우저 확장 프로그램
애플리케이션 외부의 코드가 하이드레이션을 시작하기 전에 서버에서 받은 HTML을 변경할 수 있습니다. 이런 오류는 로컬 서버에서 발생하지 않기 때문에 개발자의 컴퓨터에서는 확인하기 어려운 경우가 많습니다.
HTML을 변경하는 CDN. 일부 CDN 기능은 HTML이 브라우저에 전달되는 도중에 내용을 변경합니다.
예를 들어, 하이드레이션에 필요한 주석 노드와 공백을 제거하는 기능이 있습니다.
Cloudflare에서 기본으로 활성화되는 이메일 주소 난독화(Email Address Obfuscation)도 그 예입니다.
이 기능은 이메일 주소를 <span class="__cf_email__" data-cfemail="..."> 같은 엘리먼트로 바꿉니다.
또한 mailto: 링크를 변경하고 내용을 원래대로 되돌리는 스크립트를 추가합니다.
하이드레이션이 시작되기 전에 이 스크립트가 실행되지 않으면 Angular는 텍스트가 있어야 할 위치에서 엘리먼트를 발견합니다.
로컬 서버는 CDN을 사용하지 않으므로 애플리케이션은 개발자의 컴퓨터에서는 동작하고 프로덕션에서만 실패합니다.
브라우저 확장 프로그램. 일부 브라우저 확장 프로그램은 하이드레이션이 시작되기 전에 페이지를 변경합니다. 클릭하여 전화 걸기 도구, 번역기, 비밀번호 관리자 등이 그 예입니다. 이런 확장 프로그램은 애플리케이션에서 제어할 수 없습니다. 설치한 사용자에게만 문제가 발생하므로 일부 사용자에게서만 오류가 보고될 수 있습니다.
이런 오류를 해결하려면 다음과 같이 하세요:
- 변경되는 콘텐츠를 감싸는 컴포넌트 호스트에
ngSkipHydration을 추가하세요. 해당 콘텐츠를 작성하는 템플릿에<app-contact ngSkipHydration><a href="tel:...">...</a></app-contact>처럼 추가하세요. 그러면 Angular가 브라우저에서 이 콘텐츠를 다시 렌더링하여 외부에서 변경한 내용을 제거합니다. 이 어트리뷰트는 컴포넌트 호스트 엘리먼트에만 사용할 수 있으므로<a>엘리먼트에는 추가할 수 없습니다. 자세한 내용은 하이드레이션 건너뛰기를 참고하세요. - 애플리케이션에서 해당 CDN 기능을 끄세요. Cloudflare에서는 보안 설정에서 이메일 주소 난독화를 끄거나, 구성 규칙을 사용하여 애플리케이션의 호스트 이름에 대해 이 기능을 끄세요.
참고: Angular 컴파일러는 템플릿에서 주석을 제거하므로 Cloudflare의 <!--email_off--> 주석은 Angular 템플릿에서 도움이 되지 않습니다.
점진적 하이드레이션
점진적 하이드레이션은 하이드레이션 시점을 더 세밀하게 제어하는 고급 하이드레이션 방식입니다. 자세한 내용은 점진적 하이드레이션 가이드를 참고하세요.