# 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