# Angular @defer in 2026: Declarative Lazy Loading for Faster Apps > Master Angular @defer blocks for declarative lazy loading. Deep dive into triggers, prefetching, incremental hydration, SSR behavior, and real-world performance patterns. - Published: 2026-06-14 - Updated: 2026-06-14 - Author: SharpSkill - Tags: angular, angular-defer, lazy-loading, performance, ssr, signals, deep-dive - Reading time: 9 min --- Angular `@defer` blocks solve a problem that router-based lazy loading never could: loading individual components, directives, and pipes on demand without restructuring the application into separate routes. Introduced in Angular 17 and stabilized through Angular 22, `@defer` turns what used to require dynamic imports, `*ngIf` flags, and webpack configuration into a single declarative template block. > **What @defer does** > > `@defer` instructs the Angular compiler to split wrapped components into separate JavaScript chunks, loaded only when a trigger condition fires. The browser downloads less JavaScript upfront, improving Largest Contentful Paint (LCP) and Time to First Byte (TTFB) without manual code-splitting configuration. ## How @defer Works Under the Hood When the Angular compiler encounters a `@defer` block, it extracts every standalone component, directive, and pipe inside it into a separate chunk. The main bundle ships without these dependencies. At runtime, Angular evaluates the trigger condition and fetches the chunk via a dynamic `import()` call. The block supports four sub-blocks that control what the user sees during the loading lifecycle: ```typescript // app.component.html @defer (on viewport) { } @placeholder {
} @loading (minimum 200ms) { } @error {

Failed to load the dashboard. Please refresh.

} ``` `@placeholder` renders before the trigger fires. `@loading` appears while the chunk downloads, with an optional `minimum` duration to prevent flicker. `@error` catches network failures or chunk errors. The `minimum` parameter on `@loading` avoids a flash of spinner when the chunk loads quickly from cache. One constraint: every dependency inside `@defer` must be standalone. Non-standalone components declared in an `NgModule` cannot be deferred and will be eagerly loaded regardless of the `@defer` wrapper. ## Trigger Types: Controlling When Components Load Angular provides seven built-in triggers, each targeting a different loading strategy. Multiple triggers can be combined with semicolons and evaluate as OR conditions. ```typescript // product-page.component.html // Loads when the browser goes idle (default) @defer { } // Loads when the element enters the viewport @defer (on viewport) { } // Loads on click or keydown @defer (on interaction) { } @placeholder { } // Loads on mouseenter or focusin @defer (on hover) { } // Loads after 3 seconds @defer (on timer(3s)) { } // Loads immediately after initial render @defer (on immediate) { } ``` The `on viewport` trigger uses the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) internally. The `on idle` trigger delegates to `requestIdleCallback`, making it the safest default for below-the-fold content. The `on timer` trigger accepts durations in milliseconds (`500ms`) or seconds (`3s`). ### The `when` Trigger for Reactive Conditions Beyond event-based triggers, `when` accepts any boolean expression, including signal reads: ```typescript // dashboard.component.ts export class DashboardComponent { showAdvanced = signal(false); } // dashboard.component.html @defer (when showAdvanced()) { } @placeholder { } ``` Combining `on` and `when` is valid. Angular treats them as OR: the block loads when either condition is met. ## Prefetching: Separating Download from Display Prefetching decouples when the chunk downloads from when the component renders. This eliminates perceived latency by fetching the JavaScript before the user triggers it. ```typescript // settings.component.html @defer (on interaction; prefetch on idle) { } @placeholder { } ``` In this pattern, the browser downloads the `account-settings` chunk during idle time. When the user clicks, the component renders instantly because the code is already available. Prefetching accepts the same trigger types as the main `on` clause. A common pattern for dashboards: prefetch heavy components on idle while deferring render to viewport entry. ```typescript // analytics.component.html @defer (on viewport; prefetch on idle) { } @placeholder (minimum 100ms) {
} ``` The `minimum` on `@placeholder` prevents a layout flash when the user scrolls fast and the deferred component loads almost immediately. ## @defer and Server-Side Rendering On the server, `@defer` blocks render the `@placeholder` content by default. The deferred component is never server-rendered. This behavior is intentional: the chunk download only makes sense in a browser context. For SEO-critical content, this means `@placeholder` must contain meaningful HTML, not just a spinner: ```typescript // blog-post.component.html @defer (on viewport) { } @placeholder {

Comments

Loading comments...

} ``` Search engines index the placeholder content during SSR. A descriptive placeholder ensures the page remains semantically complete. ## Incremental Hydration with @defer Angular 19 introduced [incremental hydration](https://angular.dev/guide/incremental-hydration) in developer preview, and Angular 22 stabilizes it as a production-ready feature. Incremental hydration extends `@defer` with a `hydrate` trigger that controls when a server-rendered component becomes interactive on the client. ```typescript // product-page.component.html @defer (hydrate on viewport) { } @defer (hydrate on interaction) { } ``` Unlike standard `@defer`, incremental hydration renders the full component HTML on the server. The client receives complete markup but skips hydrating the component until the trigger fires. The result: the browser paints the full page immediately, but JavaScript execution is deferred. The Angular team reports consistent 40-50% improvements in LCP scores when using incremental hydration on content-heavy pages. Angular DevTools in version 22 visualizes hydration states (pending, hydrated, error) directly in the component inspector. ## Real-World Patterns and Performance Strategy ### Pattern 1: Heavy Dashboard with Multiple Deferred Panels ```typescript // dashboard.component.html
@defer (on viewport; prefetch on idle) { } @placeholder { } @defer (on viewport; prefetch on idle) { } @placeholder { } @defer (on interaction) { } @placeholder { }
``` Each panel loads independently. The export panel stays unloaded until the user explicitly requests it, saving a full chunk download for users who never export. ### Pattern 2: Conditional Feature Loading with Signals ```typescript // editor.component.ts export class EditorComponent { user = inject(UserService).currentUser; isPremium = computed(() => this.user()?.plan === 'premium'); } // editor.component.html @defer (when isPremium()) { } @placeholder { } ``` Free-tier users never download the AI assistant chunk. The upgrade banner serves as both placeholder and conversion prompt. ### Avoiding Common Pitfalls Nesting `@defer` blocks with the same trigger causes simultaneous chunk downloads. Stagger triggers across nested blocks: ```typescript // Avoid: both fire on idle simultaneously @defer { @defer { } } // Better: outer on idle, inner on viewport @defer (on idle) { } // Inside wrapper-component template: @defer (on viewport) { } ``` Another pitfall: using `@defer` for components already in the critical rendering path. Deferring above-the-fold content increases LCP because the browser must download and execute the chunk before painting. Reserve `@defer` for below-the-fold or user-triggered content. ## @defer vs Router-Based Lazy Loading Router-based lazy loading and `@defer` complement each other. Router lazy loading splits at the route level, deferring entire feature modules. `@defer` splits within a template, deferring individual components. | Aspect | Router Lazy Loading | @defer | |--------|-------------------|--------| | Granularity | Route level | Component level | | Trigger | Navigation event | viewport, idle, interaction, hover, timer, condition | | SSR behavior | Full server render | Placeholder on server | | Requires standalone | No (works with NgModules) | Yes (standalone only) | | Prefetching | `preloadingStrategy` | `prefetch on` clause | | Use case | Feature modules, pages | Widgets, panels, conditional UI | A typical application uses both: router lazy loading for top-level feature areas and `@defer` for heavy components within those areas. ## Interview Questions on Angular @defer Technical interviews increasingly cover `@defer` as it becomes central to Angular performance strategy. Here are patterns that commonly appear in [Angular interview questions](/technologies/angular/interview-questions/standalone-components). **Q: What happens if a non-standalone component is placed inside @defer?** The component loads eagerly regardless of the `@defer` block. The Angular compiler cannot extract NgModule-declared components into separate chunks. No error is thrown, but the optimization silently fails. **Q: How does @defer behave during SSR?** The server renders the `@placeholder` content. The deferred component is never rendered server-side. Hydration occurs on the client when the trigger fires (or via `hydrate on` for incremental hydration). **Q: Can @defer be used with OnPush change detection?** Yes. `@defer` is a template-level feature and works with any change detection strategy. The deferred component follows its own change detection configuration once loaded. **Q: What is the difference between `on idle` and `on immediate`?** `on idle` waits for the browser to finish all pending work via `requestIdleCallback`. `on immediate` triggers right after Angular completes the initial render pass, without waiting for idle state. `on immediate` is useful for components that should load soon but not block the first paint. For a broader set of Angular interview preparation, see the [Angular change detection module](/technologies/angular/interview-questions/change-detection) and the [Angular signals module](/technologies/angular/interview-questions/angular-signals). ## Conclusion - `@defer` splits standalone components into separate chunks loaded on demand, reducing initial bundle size without route restructuring - Seven trigger types (`idle`, `viewport`, `interaction`, `hover`, `timer`, `immediate`, `when`) cover every loading strategy from passive to user-driven - `prefetch on` decouples chunk download from component rendering, eliminating perceived latency on user-triggered components - Incremental hydration (`hydrate on`) renders full HTML on the server while deferring JavaScript execution to the client, delivering 40-50% LCP improvements on content-heavy pages - `@placeholder` content renders during SSR and must be semantically meaningful for SEO - `@defer` complements router-based lazy loading: routes for feature-level splitting, `@defer` for component-level splitting within templates - Non-standalone components cannot be deferred and will silently load eagerly --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/angular/angular-defer-loading-declarative-lazy-loading-guide