skip to content

In Angular, what does adding hydrate on viewport to a @defer block change on the server and in the browser?

level: middleimportance: must knowfreq 50%

answer

  1. main content instead of placeholder
  2. visible but inert
  3. code fetched on trigger
  4. hydrate in place, replay events

basics

~20 s

The server renders the block's main content instead of its @placeholder. In the browser that HTML stays dehydrated, with its code not yet loaded, until the block enters the viewport; then Angular fetches the code and hydrates it in place.

solid answer

~40 s

A `hydrate` trigger turns a `@defer` block into an incremental hydration boundary. On the server, Angular loads the block's dependencies and renders its main template rather than the `@placeholder`, so the HTML already contains, say, the comments section of a long product page. In the browser the block stays dehydrated: visible, but with no listeners attached and its component code not yet downloaded. When the block enters the viewport, Angular fetches the dependencies and hydrates the existing DOM in place, without a layout shift, and replays any queued events. The gain is less startup JavaScript and `@defer` that works above the fold; the cost is that the block is inert until its trigger fires.

code

ts · 21 lines
ts
import { Component, input } from '@angular/core';
import { ProductComments } from './product-comments';

@Component({
  selector: 'app-product-page',
  imports: [ProductComments],
  template: `
    <h1>{{ name() }}</h1>
    <!-- ...gallery, price, description... -->

    @defer (hydrate on viewport) {
      <app-product-comments [productId]="productId()" />
    } @placeholder {
      <p>Comments</p>
    }
  `,
})
export class ProductPage {
  productId = input.required<string>();
  name = input.required<string>();
}

go deeper

for a junior

Recall that a hydrate trigger makes the server render the real content and that the block becomes interactive only when the trigger fires.

for a middle

Explain what dehydrated means for DOM, code and listeners, how the trigger fetches code and hydrates in place, and how event replay covers early clicks.

for a senior

Judge where the saving is real by checking chunking, and spot blocks whose late interactivity would hurt users.

for a principal

Weigh startup cost against delayed interactivity across a page's sections and set conventions for where hydration boundaries belong.

## The block before incremental hydration A `@defer` block normally delays loading its content. During server rendering, a block with only regular triggers renders its **`@placeholder`**, and the browser later swaps in the real content when a trigger such as `on viewport` fires. For content that is visible on first paint, that swap is a layout shift. ## What a hydrate trigger changes Adding a trigger that starts with `hydrate` makes the block an **incremental hydration boundary**. Take a long product page whose comments section sits far below the fold: ```html @defer (hydrate on viewport) { <app-product-comments [productId]="productId()" /> } @placeholder { <p>Comments</p> } ``` The trigger changes behaviour on both sides: | Stage | Without a hydrate trigger | With `hydrate on viewport` | | --- | --- | --- | | Server render | Renders `@placeholder` | Loads the dependencies and renders the **main** content | | Initial HTML in the browser | Placeholder | The real comments, as HTML | | Initial JavaScript | Block code loaded when the regular trigger fires | Block code **not** loaded yet | | Interactivity | After the swap | After the block hydrates | | When the trigger fires | Content replaces placeholder | Code is fetched, then the existing DOM is hydrated in place | So the user sees the comments immediately, search engines receive them in the HTML, and the page does not pay for the comments component's JavaScript until the section scrolls into view. ## What "dehydrated" means Between first paint and the trigger, the block is **dehydrated**: - its DOM is the server's HTML, left untouched; - its component code may not have been downloaded; - its listeners are not attached, so it is not interactive yet; - events on it that Angular listens for are captured by event replay and replayed after the block hydrates. When the trigger fires, Angular fetches the block's dependencies and hydrates the existing nodes rather than re-creating them, so there is no layout shift. ## The trade-off - **Gain:** less JavaScript downloaded and executed at startup, and `@defer` becomes usable for above-the-fold content without placeholder swaps. - **Cost:** a dehydrated block is inert until its trigger fires. A trigger that fires late for a control the user needs early produces a click that waits for a download. - **Constraint:** everything full hydration requires still applies inside the block, including identical server and client DOM. ## Where the code savings come from The savings only exist if the block's components are actually split out. A component that is also imported eagerly elsewhere on the page is already in the main bundle, so deferring its hydration still saves execution work but not download size. ## Checking the result - In the served HTML, the block's main content should be present, not the placeholder. - In the network panel, the block's chunk should appear only when the trigger fires. - Interacting with the block before its chunk arrives should still work once it hydrates, because event replay queues the event. - In a performance profile, startup scripting time should drop by roughly the cost of the deferred components, if they were split out. ## What to say in an interview 1. A `hydrate` trigger makes the server render the block's main content instead of the placeholder. 2. In the browser that content stays dehydrated: visible, not yet interactive, code not yet loaded. 3. The trigger fetches the code and hydrates in place; queued events are replayed. 4. Choose the trigger by when the block must become interactive, not by when it must become visible, because it is already visible.

  • What does the server render for a @defer block that has only regular triggers?
    Its `@placeholder`. Only a `hydrate` trigger makes the server load the block's dependencies and render the main content, which is why incremental hydration lets `@defer` be used for visible content without a placeholder swap.
  • What happens to a click inside a dehydrated block?
    Event replay captures events that Angular listens for and replays them after the block, and any dehydrated parent blocks, have hydrated. The click is not lost, but its handler runs only once the code is loaded.
  • Why might hydrate on viewport save no download at all for a block?
    If the block's components are also imported eagerly elsewhere, they are already in the main bundle. Deferring hydration then saves execution work but not bytes, so check the build's chunks before counting on the saving.

A shop window display: customers see the goods from the street at once, but a sales assistant only comes over to switch on the demo unit when someone walks up to it.

saying these in an interview costs you the question

  • The server renders the @placeholder for blocks with hydrate triggers
  • A dehydrated block is invisible until its trigger fires
  • When the trigger fires, the block's DOM is destroyed and re-created
  • A dehydrated block's handlers already work before it hydrates
  • Hydrate triggers make the block's component render only in the browser