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.

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.
@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:
@defer (on viewport) {
<analytics-dashboard />
} @placeholder {
<div class="h-64 bg-muted animate-pulse"></div>
} @loading (minimum 200ms) {
<loading-spinner />
} @error {
<p>Failed to load the dashboard. Please refresh.</p>
}@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.
// Loads when the browser goes idle (default)
@defer {
<recommendation-engine />
}
// Loads when the element enters the viewport
@defer (on viewport) {
<customer-reviews [productId]="product.id" />
}
// Loads on click or keydown
@defer (on interaction) {
<product-configurator />
} @placeholder {
<button>Configure this product</button>
}
// Loads on mouseenter or focusin
@defer (on hover) {
<quick-preview />
}
// Loads after 3 seconds
@defer (on timer(3s)) {
<chat-widget />
}
// Loads immediately after initial render
@defer (on immediate) {
<notification-center />
}The on viewport trigger uses the 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:
export class DashboardComponent {
showAdvanced = signal(false);
}
// dashboard.component.html
@defer (when showAdvanced()) {
<advanced-analytics />
} @placeholder {
<button (click)="showAdvanced.set(true)">Show advanced analytics</button>
}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.
@defer (on interaction; prefetch on idle) {
<account-settings />
} @placeholder {
<button>Open settings</button>
}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.
@defer (on viewport; prefetch on idle) {
<chart-widget [data]="salesData()" />
} @placeholder (minimum 100ms) {
<div class="h-48 bg-muted rounded-none"></div>
}The minimum on @placeholder prevents a layout flash when the user scrolls fast and the deferred component loads almost immediately.
Ready to ace your Angular interviews?
Practice with our interactive simulators, flashcards, and technical tests.
@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:
@defer (on viewport) {
<comment-section [postId]="post.id" />
} @placeholder {
<section aria-label="Comments">
<h2>Comments</h2>
<p>Loading comments...</p>
</section>
}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 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.
@defer (hydrate on viewport) {
<product-gallery [images]="product.images" />
}
@defer (hydrate on interaction) {
<product-reviews [productId]="product.id" />
}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
<div class="grid grid-cols-2 gap-4">
@defer (on viewport; prefetch on idle) {
<sales-chart />
} @placeholder {
<skeleton-card height="300px" />
}
@defer (on viewport; prefetch on idle) {
<user-activity-feed />
} @placeholder {
<skeleton-card height="300px" />
}
@defer (on interaction) {
<export-panel />
} @placeholder {
<button>Export data</button>
}
</div>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
export class EditorComponent {
user = inject(UserService).currentUser;
isPremium = computed(() => this.user()?.plan === 'premium');
}
// editor.component.html
@defer (when isPremium()) {
<ai-assistant />
} @placeholder {
<upgrade-banner />
}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:
// Avoid: both fire on idle simultaneously
@defer {
@defer {
<nested-heavy-component />
}
}
// Better: outer on idle, inner on viewport
@defer (on idle) {
<wrapper-component />
}
// Inside wrapper-component template:
@defer (on viewport) {
<nested-heavy-component />
}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.
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 and the Angular signals module.
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Conclusion
@defersplits 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 ondecouples 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 @placeholdercontent renders during SSR and must be semantically meaningful for SEO@defercomplements router-based lazy loading: routes for feature-level splitting,@deferfor component-level splitting within templates- Non-standalone components cannot be deferred and will silently load eagerly
Start practicing!
Test your knowledge with our interview simulators and technical tests.
Tags
Share
Related articles

Angular 19 Interview Questions: Signals, SSR and Must-Know Concepts
The most common Angular 19 interview questions: Signals, incremental hydration, zoneless change detection, and new reactive APIs with code examples and expected answers.

Angular 19 Zoneless: Performance and Change Detection Without Zone.js
Angular zoneless change detection removes Zone.js to deliver smaller bundles, faster rendering, and explicit reactivity through signals. This deep dive covers the migration path from Zone.js to zoneless Angular, from provideExperimentalZonelessChangeDetection in Angular 19 to the stable API in Angular 20+.

Angular 20 in 2026: Resource API, httpResource and Interview Questions
Angular 20 introduces httpResource and stabilizes the Resource API for signal-based data fetching. A hands-on tutorial covering resource(), rxResource(), httpResource(), Zod validation, and common interview questions.