심화 가이드
서버 사이드 & 하이브리드 렌더링

점진적 하이드레이션

점진적 하이드레이션은 애플리케이션의 일부 영역을 하이드레이션되지 않은 상태로 두었다가, 필요할 때 점진적으로 하이드레이션하는 고급 하이드레이션 방식입니다.

점진적 하이드레이션은 왜 사용하나요?

점진적 하이드레이션은 애플리케이션 전체 하이드레이션을 기반으로 성능을 개선하는 기능입니다. 애플리케이션 전체를 하이드레이션할 때와 비슷한 사용자 경험을 제공하면서 초기 번들 크기를 줄일 수 있습니다. 번들이 작아지면 초기 로드 시간이 개선되어 최초 입력 지연(FID)과 누적 레이아웃 이동(CLS)이 줄어듭니다.

점진적 하이드레이션을 사용하면 이전에는 지연하기 어려웠던 콘텐츠에도 지연 가능한 뷰(@defer)를 사용할 수 있습니다. 특히 스크롤하지 않아도 보이는 첫 화면의 콘텐츠에도 사용할 수 있습니다. 점진적 하이드레이션이 없을 때는 첫 화면에 @defer 블록을 두면 플레이스홀더가 먼저 렌더링된 뒤 @defer 블록의 메인 템플릿 콘텐츠로 바뀌면서 레이아웃 이동이 발생했습니다. 점진적 하이드레이션을 사용하면 @defer 블록의 메인 템플릿을 렌더링하고 하이드레이션할 때 레이아웃이 이동하지 않습니다.

Angular에서 점진적 하이드레이션 활성화하기

하이드레이션을 사용하는 서버 사이드 렌더링(SSR) 애플리케이션에서 점진적 하이드레이션을 활성화할 수 있습니다. 먼저 Angular SSR 가이드에 따라 서버 사이드 렌더링을 활성화하고, Angular 하이드레이션 가이드에 따라 하이드레이션을 활성화하세요.

provideClientHydration()을 사용하면 점진적 하이드레이션은 기본적으로 활성화됩니다.

import {bootstrapApplication, provideClientHydration} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [provideClientHydration()],
});

참고: 점진적 하이드레이션은 이벤트 재실행에 의존하며 이 기능을 자동으로 활성화합니다. 목록에 withEventReplay()가 이미 있다면 제거해도 됩니다.

점진적 하이드레이션을 비활성화하려면 withNoIncrementalHydration()을 사용하세요:

점진적 하이드레이션은 어떻게 동작하나요?

점진적 하이드레이션은 애플리케이션 전체 하이드레이션, 지연 가능한 뷰, 이벤트 재실행을 기반으로 동작합니다. 점진적 하이드레이션을 사용하면 @defer 블록에 추가 트리거를 지정해서 점진적 하이드레이션의 경계를 정의할 수 있습니다. 지연 블록에 hydrate 트리거를 추가하면 Angular는 서버 사이드 렌더링 중 해당 블록의 의존성을 로드하고 @placeholder 대신 메인 템플릿을 렌더링합니다. 클라이언트 사이드 렌더링에서는 의존성 로드를 계속 미루며, 블록의 콘텐츠는 hydrate 트리거가 실행될 때까지 하이드레이션되지 않은 상태로 남습니다. 트리거가 실행되면 지연 블록은 의존성을 가져오고 콘텐츠를 하이드레이션합니다. 하이드레이션 전에 사용자가 발생시킨 브라우저 이벤트 중 컴포넌트에 등록된 리스너와 일치하는 이벤트는 대기열에 저장되었다가 하이드레이션이 완료되면 재실행됩니다.

트리거로 콘텐츠 하이드레이션 제어하기

하이드레이션 트리거를 지정하면 Angular가 지연된 콘텐츠를 로드하고 하이드레이션하는 시점을 제어할 수 있습니다. 일반 @defer 트리거와 함께 사용할 수 있는 추가 트리거입니다.

각 @defer 블록에는 세미콜론(;)으로 구분해서 하이드레이션 이벤트 트리거를 여러 개 지정할 수 있습니다. 이 중 하나라도 실행되면 Angular가 하이드레이션을 시작합니다.

하이드레이션 트리거에는 hydrate on, hydrate when, hydrate never 세 가지 종류가 있습니다.

hydrate on

hydrate on은 @defer 블록의 하이드레이션이 실행될 조건을 지정합니다.

사용할 수 있는 트리거는 다음과 같습니다:

트리거 설명
hydrate on idle 브라우저가 유휴 상태가 되면 실행합니다. 선택적으로 타임아웃을 지정할 수 있습니다.
hydrate on viewport 지정한 콘텐츠가 뷰포트에 들어오면 실행합니다.
hydrate on interaction 사용자가 지정한 엘리먼트와 상호작용하면 실행합니다.
hydrate on hover 마우스가 지정한 영역 위에 올라오면 실행합니다.
hydrate on immediate 지연하지 않은 콘텐츠의 렌더링이 끝나면 즉시 실행합니다.
hydrate on timer 지정한 시간이 지나면 실행합니다.

hydrate on idle

hydrate on idle 트리거는 requestIdleCallback을 기준으로 브라우저가 유휴 상태에 도달하면 지연 가능한 뷰의 의존성을 로드하고 콘텐츠를 하이드레이션합니다.

requestIdleCallback에 전달할 타임아웃을 밀리초 단위로 지정할 수도 있습니다. 브라우저가 콜백을 충분히 빨리 실행하도록 예약하지 않더라도, 작업은 지정한 타임아웃 안에 실행됩니다.

@defer (hydrate on idle) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

<!-- 500밀리초 타임아웃 지정 -->
@defer (hydrate on idle(500)) {
  <large-cmp />
}

hydrate on viewport

hydrate on viewport 트리거는 Intersection Observer API로 지정한 콘텐츠가 뷰포트에 들어오는지 감지한 뒤, 지연 가능한 뷰의 의존성을 로드하고 애플리케이션의 해당 페이지를 하이드레이션합니다.

@defer (hydrate on viewport) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

hydrate on interaction

hydrate on interaction 트리거는 사용자가 지정한 엘리먼트에서 click이나 keydown 이벤트로 상호작용할 때 지연 가능한 뷰의 의존성을 로드하고 콘텐츠를 하이드레이션합니다.

@defer (hydrate on interaction) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

hydrate on hover

hydrate on hover 트리거는 mouseover와 focusin 이벤트를 통해 지정한 영역에 마우스가 올라온 것을 감지하면 지연 가능한 뷰의 의존성을 로드하고 콘텐츠를 하이드레이션합니다.

@defer (hydrate on hover) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

hydrate on immediate

hydrate on immediate 트리거는 지연 가능한 뷰의 의존성을 즉시 로드하고 콘텐츠를 하이드레이션합니다. 즉, 지연하지 않은 다른 모든 콘텐츠의 렌더링이 끝나는 즉시 지연 블록을 로드합니다.

@defer (hydrate on immediate) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

hydrate on timer

hydrate on timer 트리거는 지정한 시간이 지나면 지연 가능한 뷰의 의존성을 로드하고 콘텐츠를 하이드레이션합니다.

@defer (hydrate on timer(500ms)) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

시간 파라미터는 밀리초(ms) 또는 초(s) 단위로 지정해야 합니다.

hydrate when

hydrate when 트리거는 커스텀 조건 표현식을 받아, 조건이 참으로 평가되는 값(truthy)이 되면 지연 가능한 뷰의 의존성을 로드하고 콘텐츠를 하이드레이션합니다.

@defer (hydrate when condition) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

참고: hydrate when 조건은 해당 블록이 하이드레이션되지 않은 최상위 @defer 블록일 때만 실행됩니다. 트리거의 조건은 부모 컴포넌트에 지정되므로, 실행하려면 부모 컴포넌트가 먼저 존재해야 합니다. 부모 블록이 하이드레이션되지 않았다면 Angular는 아직 해당 표현식을 평가할 수 없습니다.

hydrate never

hydrate never를 사용하면 지연 블록의 콘텐츠를 계속 하이드레이션되지 않은 상태로 두어 사실상 정적 콘텐츠로 만들 수 있습니다. 다만 초기 렌더링에만 적용됩니다. 하이드레이션은 서버에서 렌더링한 콘텐츠를 처음 로드할 때만 적용되므로, 이후 클라이언트 사이드 렌더링에서는 hydrate never가 지정된 @defer 블록도 의존성을 가져옵니다. 아래 예제에서 이후 클라이언트 사이드 렌더링은 블록이 뷰포트에 들어올 때 @defer 블록의 의존성을 로드합니다.

@defer (on viewport; hydrate never) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

참고: hydrate never를 사용하면 해당 @defer 블록에 중첩된 하위 트리 전체가 하이드레이션되지 않습니다. 이 블록 아래에 중첩된 콘텐츠의 다른 hydrate 트리거도 실행되지 않습니다.

일반 트리거와 하이드레이션 트리거 함께 사용하기

하이드레이션 트리거는 @defer 블록의 일반 트리거와 함께 사용하는 추가 트리거입니다. 하이드레이션은 초기 로드를 최적화하는 기능이므로 하이드레이션 트리거도 초기 로드에만 적용됩니다. 이후의 클라이언트 사이드 렌더링은 일반 트리거를 사용합니다.

@defer (on idle; hydrate on interaction) {
  <example-cmp />
} @placeholder {
  <div>Example Placeholder</div>
}

이 예제에서는 처음 로드할 때 hydrate on interaction이 적용됩니다. <example-cmp /> 컴포넌트와 상호작용하면 하이드레이션이 실행됩니다. 이후 사용자가 이 컴포넌트가 있는 페이지로 이동하는 routerLink를 클릭하는 등 클라이언트에서 렌더링하는 페이지 로드에는 on idle이 적용됩니다.

중첩된 @defer 블록에서 점진적 하이드레이션은 어떻게 동작하나요?

Angular의 컴포넌트와 의존성 시스템은 계층 구조이므로, 어떤 컴포넌트를 하이드레이션하려면 모든 부모도 하이드레이션되어야 합니다. 따라서 하이드레이션되지 않은 @defer 블록이 중첩된 상태에서 자식 @defer 블록의 하이드레이션이 실행되면, 하이드레이션되지 않은 최상위 @defer 블록부터 해당 자식까지 순서대로 하이드레이션됩니다.

@defer (hydrate on interaction) {
  <parent-block-cmp />
  @defer (hydrate on hover) {
    <child-block-cmp />
  } @placeholder {
    <div>Child placeholder</div>
  }
} @placeholder {
  <div>Parent Placeholder</div>
}

위 예제에서는 중첩된 @defer 블록 위에 마우스를 올리면 하이드레이션이 실행됩니다. <parent-block-cmp />가 있는 부모 @defer 블록이 먼저 하이드레이션되고, 그다음 <child-block-cmp />가 있는 자식 @defer 블록이 하이드레이션됩니다.

제약 사항

점진적 하이드레이션에는 DOM 직접 조작 제한과 유효한 HTML 구조 요구 사항 등 애플리케이션 전체 하이드레이션과 같은 제약이 있습니다. 자세한 내용은 하이드레이션 가이드의 제약 사항을 참고하세요.

@placeholder 블록도 여전히 필요한가요?

네. @placeholder 블록의 콘텐츠는 점진적 하이드레이션에 사용되지 않지만, 이후의 클라이언트 사이드 렌더링에는 @placeholder가 여전히 필요합니다. 콘텐츠가 초기 로드에 포함된 라우팅 규칙에 없었다면, 나중에 해당 @defer 블록 콘텐츠가 있는 라우팅 규칙으로 이동할 때는 일반 @defer 블록처럼 렌더링됩니다. 따라서 이런 클라이언트 사이드 렌더링에서는 @placeholder가 렌더링됩니다.