skip to content

In Angular, for a block written @defer (on idle; hydrate on interaction), which trigger applies when, and why keep its @placeholder?

level: middleimportance: should knowfreq 30%

answer

  1. first load vs navigation
  2. hydration is initial-load only
  3. placeholder serves the client path
  4. hydrate never has the same split

basics

~10 s

hydrate on interaction applies only on the initial server-rendered load; after client-side navigation the block uses on idle and shows its @placeholder first. The placeholder is therefore still required for the client path.

solid answer

~40 s

Hydrate triggers and regular triggers answer different situations. On the initial load of a server-rendered page, the block's main content arrives as HTML and stays dehydrated until the `hydrate` trigger fires, here the first click or key press on it. Hydration only applies to that server-produced DOM, so on any later client-side render, for example after a router link brings the user to the same page, there is no server HTML and the regular triggers apply: the `@placeholder` renders and the content loads `on idle`. That is why `@placeholder` is still needed even though incremental hydration never shows it on first load. `hydrate never` follows the same split: static on first load, regular triggers afterwards.

code

html · 11 lines
html
@defer (on idle; hydrate on interaction) {
  <app-size-picker />
} @placeholder {
  <div class="size-picker-skeleton"></div>
}

@defer (on viewport; hydrate never) {
  <app-legal-footer />
} @placeholder {
  <footer class="footer-skeleton"></footer>
}

go deeper

for a junior

Recall that hydrate triggers apply to the first server-rendered load and regular triggers to later client-side renders.

for a middle

Explain why hydration is an initial-load optimisation, what renders on navigation, and why @placeholder must stay.

for a senior

Design both paths deliberately, test navigation as well as first loads, and choose placeholders that fit the client-side case.

for a principal

Make sure performance budgets and tests cover both the server-rendered entry and in-app navigation, since blocks behave differently on each.

## Two sets of triggers on one block A `@defer` block can carry **regular triggers** (`on idle`, `on viewport`, `when ...`), **prefetch triggers**, and **hydrate triggers** (`hydrate on ...`, `hydrate when ...`, `hydrate never`). They are separated with semicolons: ```html @defer (on idle; hydrate on interaction) { <app-size-picker /> } @placeholder { <div class="size-picker-skeleton"></div> } ``` The two sets answer different questions: | Situation | Which triggers apply | What renders first | | --- | --- | --- | | Initial load of a server-rendered page | The **hydrate** triggers | The main content, from the server HTML | | Client-side navigation to a page containing the block | The **regular** triggers | The `@placeholder` | | A page the browser renders itself (for example a client-rendered route) | The **regular** triggers | The `@placeholder` | For the example: on a first visit that was server-rendered, the size picker arrives as HTML and hydrates when the user clicks or presses a key on it. If the user reaches the same page later through a router link, no server HTML exists for it, so the block behaves like any deferred block and loads `on idle`. ## Why hydrate triggers are "initial load only" Hydration is by definition about **server-produced DOM that already exists**. After the first page, the app renders in the browser; there is no dehydrated HTML to activate, so hydrate triggers have nothing to do. The guide calls hydration an **initial-load optimisation** for this reason. ## Why @placeholder is still needed It is tempting to drop `@placeholder` because incremental hydration never shows it on first load. That breaks the second row of the table: on client-side navigation the block renders like a regular deferred block, and without a placeholder there is nothing to show (and nothing for `on viewport` or `on interaction` to observe) until the content loads. So: - keep a `@placeholder` that fits the space the content will take; - design it for the navigation case, not the initial-load case. ## hydrate never and regular triggers `hydrate never` follows the same rule: ```html @defer (on viewport; hydrate never) { <app-legal-footer /> } @placeholder { <footer class="footer-skeleton"></footer> } ``` - On the initial server-rendered load the footer stays static HTML forever; its code is not loaded for hydration. - On a later client-side render, `on viewport` applies and the block loads normally. ## Common mistakes 1. Writing only `hydrate on ...` triggers and assuming navigation behaves the same way; without an explicit regular trigger, a client-side render falls back to the default `@defer` trigger, `on idle`. 2. Removing `@placeholder` because "incremental hydration does not use it". 3. Expecting `hydrate on interaction` to wait for interaction after a client-side navigation; after navigation only the regular triggers count. 4. Testing only first loads, so the navigation path, where the placeholder and regular triggers matter, is never exercised. ## Testing both paths - **Entry test:** load the page directly from the server and check that the main content is in the HTML and that the block hydrates on its hydrate trigger. - **Navigation test:** reach the same page through a router link and check that the placeholder appears and the regular trigger loads the content. - **Placeholder shape:** make sure the placeholder occupies roughly the same space as the content, because on navigation it is what users see first. ## Recap - **Hydrate triggers**: first load of server HTML, main content visible from the start. - **Regular triggers**: every client-side render, placeholder first. - **`@placeholder`**: still required, for the client-side path.

  • Why do hydrate triggers not apply after client-side navigation?
    Hydration activates DOM that the server produced. After the first page the app renders in the browser, so there is no dehydrated HTML to activate and only the regular triggers are meaningful.
  • What does a hydrate never block do on a later client-side render?
    It behaves like any deferred block: the placeholder renders and its regular trigger, for example `on viewport`, loads the content. `hydrate never` only keeps the block static on the initial server-rendered load.

saying these in an interview costs you the question

  • Hydrate triggers also control loading after router navigation
  • @placeholder is unnecessary once a block has a hydrate trigger
  • hydrate never means the block is never loaded in any render
  • The regular trigger decides when the server-rendered block hydrates
  • A block cannot have both regular and hydrate triggers