Angular @defer ์™„์ „ ๊ฐ€์ด๋“œ 2026: ์„ ์–ธ์  ์ง€์—ฐ ๋กœ๋”ฉ๊ณผ ๋ฉด์ ‘ ์งˆ๋ฌธ

Angular @defer ๋ธ”๋ก์„ ํ™œ์šฉํ•œ ์„ ์–ธ์  ์ง€์—ฐ ๋กœ๋”ฉ์˜ ์ž‘๋™ ์›๋ฆฌ, ํŠธ๋ฆฌ๊ฑฐ ์œ ํ˜•, ํ”„๋ฆฌํŽ˜์น˜ ์ „๋žต, ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜, ๋ฉด์ ‘ ์งˆ๋ฌธ์„ ์ƒ์„ธํžˆ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

Angular @defer ์„ ์–ธ์  ์ง€์—ฐ ๋กœ๋”ฉ ๊ฐ€์ด๋“œ

Angular์˜ @defer ๋ธ”๋ก์€ ๋ผ์šฐํ„ฐ ๊ธฐ๋ฐ˜ ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ๋Š” ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†์—ˆ๋˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ณ„๋„์˜ ๋ผ์šฐํŠธ๋กœ ์žฌ๊ตฌ์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ๊ฐœ๋ณ„ ์ปดํฌ๋„ŒํŠธ, ๋””๋ ‰ํ‹ฐ๋ธŒ, ํŒŒ์ดํ”„๋ฅผ ์˜จ๋””๋งจ๋“œ๋กœ ๋กœ๋”ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Angular 17์—์„œ ๋„์ž…๋˜๊ณ  Angular 22์—์„œ ์•ˆ์ •ํ™”๋œ @defer๋Š” ๋™์  ์ž„ํฌํŠธ, *ngIf ํ”Œ๋ž˜๊ทธ, webpack ์„ค์ •์„ ์กฐํ•ฉํ•ด์•ผ ํ–ˆ๋˜ ์ž‘์—…์„ ํ•˜๋‚˜์˜ ์„ ์–ธ์  ํ…œํ”Œ๋ฆฟ ๋ธ”๋ก์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

@defer์˜ ์—ญํ• 

@defer๋Š” ๋ž˜ํ•‘๋œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ„๋„์˜ JavaScript ์ฒญํฌ๋กœ ๋ถ„ํ• ํ•˜๊ณ , ํŠธ๋ฆฌ๊ฑฐ ์กฐ๊ฑด์ด ๋ฐœ๋™๋  ๋•Œ๋งŒ ๋กœ๋”ฉํ•˜๋„๋ก Angular ์ปดํŒŒ์ผ๋Ÿฌ์— ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ดˆ๊ธฐ์— ๋‹ค์šด๋กœ๋“œํ•˜๋Š” JavaScript๊ฐ€ ์ค„์–ด๋“ค์–ด ์ˆ˜๋™ ์ฝ”๋“œ ๋ถ„ํ•  ์„ค์ • ์—†์ด Largest Contentful Paint(LCP)์™€ Time to First Byte(TTFB)๊ฐ€ ๊ฐœ์„ ๋ฉ๋‹ˆ๋‹ค.

@defer์˜ ๋‚ด๋ถ€ ์ž‘๋™ ๋ฉ”์ปค๋‹ˆ์ฆ˜

Angular ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ @defer ๋ธ”๋ก์„ ๋งŒ๋‚˜๋ฉด, ๋‚ด๋ถ€์˜ ๋ชจ๋“  ์Šคํƒ ๋“œ์–ผ๋ก  ์ปดํฌ๋„ŒํŠธ, ๋””๋ ‰ํ‹ฐ๋ธŒ, ํŒŒ์ดํ”„๋ฅผ ๋ณ„๋„์˜ ์ฒญํฌ๋กœ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”์ธ ๋ฒˆ๋“ค์€ ์ด๋Ÿฌํ•œ ์˜์กด์„ฑ ์—†์ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. ๋Ÿฐํƒ€์ž„์— Angular๋Š” ํŠธ๋ฆฌ๊ฑฐ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•˜๊ณ  ๋™์  import() ํ˜ธ์ถœ์„ ํ†ตํ•ด ์ฒญํฌ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

์ด ๋ธ”๋ก์€ ๋กœ๋”ฉ ๋ผ์ดํ”„์‚ฌ์ดํด ๋™์•ˆ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ๋‚ด์šฉ์„ ์ œ์–ดํ•˜๋Š” 4๊ฐœ์˜ ์„œ๋ธŒ ๋ธ”๋ก์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค:

app.component.htmltypescript
@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๋Š” ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๋ฐœ๋™๋˜๊ธฐ ์ „์— ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. @loading์€ ์ฒญํฌ๊ฐ€ ๋‹ค์šด๋กœ๋“œ๋˜๋Š” ๋™์•ˆ ํ‘œ์‹œ๋˜๋ฉฐ, ๊นœ๋นก์ž„์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์„ ํƒ์  minimum ๊ธฐ๊ฐ„์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @error๋Š” ๋„คํŠธ์›Œํฌ ์žฅ์• ๋‚˜ ์ฒญํฌ ์˜ค๋ฅ˜๋ฅผ ํฌ์ฐฉํ•ฉ๋‹ˆ๋‹ค. @loading์˜ minimum ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์ฒญํฌ๊ฐ€ ์บ์‹œ์—์„œ ๋น ๋ฅด๊ฒŒ ๋กœ๋”ฉ๋  ๋•Œ ์Šคํ”ผ๋„ˆ๊ฐ€ ์ž ๊น ํ‘œ์‹œ๋˜๋Š” ํ˜„์ƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

์ค‘์š”ํ•œ ์ œ์•ฝ ์กฐ๊ฑด์œผ๋กœ, @defer ๋‚ด๋ถ€์˜ ๋ชจ๋“  ์˜์กด์„ฑ์€ ์Šคํƒ ๋“œ์–ผ๋ก ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. NgModule์— ์„ ์–ธ๋œ ๋น„์Šคํƒ ๋“œ์–ผ๋ก  ์ปดํฌ๋„ŒํŠธ๋Š” ์ง€์—ฐ ๋กœ๋”ฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉฐ, @defer ๋ž˜ํผ์™€ ๊ด€๊ณ„์—†์ด ์ฆ‰์‹œ ๋กœ๋”ฉ๋ฉ๋‹ˆ๋‹ค.

ํŠธ๋ฆฌ๊ฑฐ ์œ ํ˜•: ์ปดํฌ๋„ŒํŠธ ๋กœ๋”ฉ ์‹œ์  ์ œ์–ด

Angular๋Š” 7๊ฐ€์ง€ ๋‚ด์žฅ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ๊ฐ๊ฐ ๋‹ค๋ฅธ ๋กœ๋”ฉ ์ „๋žต์— ๋Œ€์‘ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์„ธ๋ฏธ์ฝœ๋ก ์œผ๋กœ ๊ฒฐํ•ฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ OR ์กฐ๊ฑด์œผ๋กœ ํ‰๊ฐ€๋ฉ๋‹ˆ๋‹ค.

product-page.component.htmltypescript
// ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์œ ํœด ์ƒํƒœ์ผ ๋•Œ ๋กœ๋”ฉ (๊ธฐ๋ณธ๊ฐ’)
@defer {
  <recommendation-engine />
}

// ์š”์†Œ๊ฐ€ ๋ทฐํฌํŠธ์— ๋“ค์–ด์˜ฌ ๋•Œ ๋กœ๋”ฉ
@defer (on viewport) {
  <customer-reviews [productId]="product.id" />
}

// ํด๋ฆญ ๋˜๋Š” ํ‚ค๋‹ค์šด ์‹œ ๋กœ๋”ฉ
@defer (on interaction) {
  <product-configurator />
} @placeholder {
  <button>Configure this product</button>
}

// ๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ๋˜๋Š” ํฌ์ปค์Šค ์‹œ ๋กœ๋”ฉ
@defer (on hover) {
  <quick-preview />
}

// 3์ดˆ ํ›„ ๋กœ๋”ฉ
@defer (on timer(3s)) {
  <chat-widget />
}

// ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์งํ›„ ๋กœ๋”ฉ
@defer (on immediate) {
  <notification-center />
}

on viewport ํŠธ๋ฆฌ๊ฑฐ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ Intersection Observer API๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. on idle ํŠธ๋ฆฌ๊ฑฐ๋Š” requestIdleCallback์— ์œ„์ž„ํ•˜๋ฏ€๋กœ ํด๋“œ ์•„๋ž˜ ์ฝ˜ํ…์ธ ์— ๊ฐ€์žฅ ์•ˆ์ „ํ•œ ๊ธฐ๋ณธ๊ฐ’์ž…๋‹ˆ๋‹ค. on timer ํŠธ๋ฆฌ๊ฑฐ๋Š” ๋ฐ€๋ฆฌ์ดˆ(500ms) ๋˜๋Š” ์ดˆ(3s) ๋‹จ์œ„๋กœ ๊ธฐ๊ฐ„์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

when ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ํ†ตํ•œ ๋ฆฌ์•กํ‹ฐ๋ธŒ ์กฐ๊ฑด

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํŠธ๋ฆฌ๊ฑฐ ์™ธ์—๋„ when์€ ์‹œ๊ทธ๋„ ์ฝ๊ธฐ๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  boolean ํ‘œํ˜„์‹์„ ๋ฐ›์•„๋“ค์ž…๋‹ˆ๋‹ค:

dashboard.component.tstypescript
export class DashboardComponent {
  showAdvanced = signal(false);
}

// dashboard.component.html
@defer (when showAdvanced()) {
  <advanced-analytics />
} @placeholder {
  <button (click)="showAdvanced.set(true)">Show advanced analytics</button>
}

on๊ณผ when์˜ ์กฐํ•ฉ์€ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค. Angular๋Š” ์ด๋ฅผ OR๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ์–ด๋А ์กฐ๊ฑด์ด๋“  ์ถฉ์กฑ๋˜๋ฉด ๋ธ”๋ก์ด ๋กœ๋”ฉ๋ฉ๋‹ˆ๋‹ค.

ํ”„๋ฆฌํŽ˜์น˜: ๋‹ค์šด๋กœ๋“œ์™€ ํ‘œ์‹œ์˜ ๋ถ„๋ฆฌ

ํ”„๋ฆฌํŽ˜์น˜๋Š” ์ฒญํฌ์˜ ๋‹ค์šด๋กœ๋“œ ์‹œ์ ๊ณผ ์ปดํฌ๋„ŒํŠธ์˜ ๋ Œ๋”๋ง ์‹œ์ ์„ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๋ฐœ๋™์‹œํ‚ค๊ธฐ ์ „์— JavaScript๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์™€ ์ฒด๊ฐ ์ง€์—ฐ ์‹œ๊ฐ„์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

settings.component.htmltypescript
@defer (on interaction; prefetch on idle) {
  <account-settings />
} @placeholder {
  <button>Open settings</button>
}

์ด ํŒจํ„ด์—์„œ ๋ธŒ๋ผ์šฐ์ €๋Š” ์œ ํœด ์‹œ๊ฐ„ ๋™์•ˆ account-settings ์ฒญํฌ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํด๋ฆญํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ์ด๋ฏธ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ฆ‰์‹œ ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค. ํ”„๋ฆฌํŽ˜์น˜๋Š” ๋ฉ”์ธ on ์ ˆ๊ณผ ๋™์ผํ•œ ํŠธ๋ฆฌ๊ฑฐ ์œ ํ˜•์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

๋Œ€์‹œ๋ณด๋“œ์—์„œ ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์œผ๋กœ, ๋ฌด๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์œ ํœด ์‹œ ํ”„๋ฆฌํŽ˜์น˜ํ•˜๊ณ  ๋ทฐํฌํŠธ ์ง„์ž… ์‹œ ๋ Œ๋”๋ง์„ ์ง€์—ฐํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

analytics.component.htmltypescript
@defer (on viewport; prefetch on idle) {
  <chart-widget [data]="salesData()" />
} @placeholder (minimum 100ms) {
  <div class="h-48 bg-muted rounded-none"></div>
}

@placeholder์˜ minimum์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋น ๋ฅด๊ฒŒ ์Šคํฌ๋กคํ•˜์—ฌ ์ง€์—ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฑฐ์˜ ์ฆ‰์‹œ ๋กœ๋”ฉ๋  ๋•Œ ๋ ˆ์ด์•„์›ƒ ๊นœ๋นก์ž„์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

Angular ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

@defer์™€ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง

์„œ๋ฒ„์—์„œ @defer ๋ธ”๋ก์€ ๊ธฐ๋ณธ์ ์œผ๋กœ @placeholder ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ์ง€์—ฐ ์ปดํฌ๋„ŒํŠธ๋Š” ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๋™์ž‘์€ ์˜๋„์ ์ธ ๊ฒƒ์œผ๋กœ, ์ฒญํฌ ๋‹ค์šด๋กœ๋“œ๋Š” ๋ธŒ๋ผ์šฐ์ € ์ปจํ…์ŠคํŠธ์—์„œ๋งŒ ์˜๋ฏธ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

SEO์— ์ค‘์š”ํ•œ ์ฝ˜ํ…์ธ ์˜ ๊ฒฝ์šฐ, @placeholder์—๋Š” ์Šคํ”ผ๋„ˆ๋งŒ์ด ์•„๋‹Œ ์˜๋ฏธ ์žˆ๋Š” HTML์ด ํฌํ•จ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

blog-post.component.htmltypescript
@defer (on viewport) {
  <comment-section [postId]="post.id" />
} @placeholder {
  <section aria-label="Comments">
    <h2>Comments</h2>
    <p>Loading comments...</p>
  </section>
}

๊ฒ€์ƒ‰ ์—”์ง„์€ SSR ์‹œ ํ”Œ๋ ˆ์ด์Šคํ™€๋” ์ฝ˜ํ…์ธ ๋ฅผ ์ธ๋ฑ์‹ฑํ•ฉ๋‹ˆ๋‹ค. ์„ค๋ช…์ ์ธ ํ”Œ๋ ˆ์ด์Šคํ™€๋”๋ฅผ ํ†ตํ•ด ํŽ˜์ด์ง€์˜ ์‹œ๋งจํ‹ฑ ์™„์ „์„ฑ์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜๊ณผ @defer

Angular 19์—์„œ ๊ฐœ๋ฐœ์ž ํ”„๋ฆฌ๋ทฐ๋กœ ๋„์ž…๋œ ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜์€ Angular 22์—์„œ ํ”„๋กœ๋•์…˜ ์ค€๋น„ ๊ธฐ๋Šฅ์œผ๋กœ ์•ˆ์ •ํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜์€ @defer๋ฅผ hydrate ํŠธ๋ฆฌ๊ฑฐ๋กœ ํ™•์žฅํ•˜์—ฌ, ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—์„œ ์–ธ์ œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒํ•˜๊ฒŒ ๋˜๋Š”์ง€ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

product-page.component.htmltypescript
@defer (hydrate on viewport) {
  <product-gallery [images]="product.images" />
}

@defer (hydrate on interaction) {
  <product-reviews [productId]="product.id" />
}

ํ‘œ์ค€ @defer์™€ ๋‹ฌ๋ฆฌ, ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜์€ ์„œ๋ฒ„์—์„œ ์™„์ „ํ•œ ์ปดํฌ๋„ŒํŠธ HTML์„ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” ์™„์ „ํ•œ ๋งˆํฌ์—…์„ ์ˆ˜์‹ ํ•˜์ง€๋งŒ ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๋ฐœ๋™๋  ๋•Œ๊นŒ์ง€ ์ปดํฌ๋„ŒํŠธ์˜ ํ•˜์ด๋“œ๋ ˆ์ด์…˜์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๋Š” ์™„์ „ํ•œ ํŽ˜์ด์ง€๋ฅผ ์ฆ‰์‹œ ๊ทธ๋ฆฌ์ง€๋งŒ, JavaScript ์‹คํ–‰์€ ์ง€์—ฐ๋ฉ๋‹ˆ๋‹ค.

Angular ํŒ€์€ ์ฝ˜ํ…์ธ ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€์—์„œ ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ LCP ์ ์ˆ˜๊ฐ€ ์ผ๊ด€๋˜๊ฒŒ 40~50% ๊ฐœ์„ ๋œ๋‹ค๊ณ  ๋ณด๊ณ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Angular 22์˜ Angular DevTools๋Š” ์ปดํฌ๋„ŒํŠธ ์ธ์ŠคํŽ™ํ„ฐ์—์„œ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์ƒํƒœ(๋Œ€๊ธฐ ์ค‘, ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์™„๋ฃŒ, ์˜ค๋ฅ˜)๋ฅผ ์ง์ ‘ ์‹œ๊ฐํ™”ํ•ฉ๋‹ˆ๋‹ค.

์‹ค์ „ ํŒจํ„ด๊ณผ ์„ฑ๋Šฅ ์ „๋žต

ํŒจํ„ด 1: ๋ณต์ˆ˜์˜ ์ง€์—ฐ ํŒจ๋„์„ ๊ฐ€์ง„ ๋Œ€์‹œ๋ณด๋“œ

dashboard.component.htmltypescript
<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>

๊ฐ ํŒจ๋„์€ ๋…๋ฆฝ์ ์œผ๋กœ ๋กœ๋”ฉ๋ฉ๋‹ˆ๋‹ค. ๋‚ด๋ณด๋‚ด๊ธฐ ํŒจ๋„์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ ์š”์ฒญํ•  ๋•Œ๊นŒ์ง€ ๋กœ๋”ฉ๋˜์ง€ ์•Š์€ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜์—ฌ, ๋‚ด๋ณด๋‚ด๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž์˜ ์ฒญํฌ ๋‹ค์šด๋กœ๋“œ๋ฅผ ์ ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค.

ํŒจํ„ด 2: ์‹œ๊ทธ๋„์„ ํ™œ์šฉํ•œ ์กฐ๊ฑด๋ถ€ ๊ธฐ๋Šฅ ๋กœ๋”ฉ

editor.component.tstypescript
export class EditorComponent {
  user = inject(UserService).currentUser;
  isPremium = computed(() => this.user()?.plan === 'premium');
}

// editor.component.html
@defer (when isPremium()) {
  <ai-assistant />
} @placeholder {
  <upgrade-banner />
}

๋ฌด๋ฃŒ ์š”๊ธˆ์ œ ์‚ฌ์šฉ์ž๋Š” AI ์–ด์‹œ์Šคํ„ดํŠธ ์ฒญํฌ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์—…๊ทธ๋ ˆ์ด๋“œ ๋ฐฐ๋„ˆ๋Š” ํ”Œ๋ ˆ์ด์Šคํ™€๋”์™€ ์ „ํ™˜ ์ด‰์ง„์˜ ๋‘ ๊ฐ€์ง€ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

ํ”ํ•œ ์‹ค์ˆ˜ ํ”ผํ•˜๊ธฐ

๋™์ผํ•œ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๊ฐ€์ง„ @defer ๋ธ”๋ก์„ ์ค‘์ฒฉํ•˜๋ฉด ๋™์‹œ์— ์ฒญํฌ๊ฐ€ ๋‹ค์šด๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. ์ค‘์ฒฉ๋œ ๋ธ”๋ก ๊ฐ„์— ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ๋ถ„์‚ฐ์‹œํ‚ค๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค:

typescript
// ํ”ผํ•ด์•ผ ํ•  ์˜ˆ: ๋‘˜ ๋‹ค ์œ ํœด ์‹œ ๋™์‹œ ๋ฐœ๋™
@defer {
  @defer {
    <nested-heavy-component />
  }
}

// ๊ถŒ์žฅ: ์™ธ๋ถ€๋Š” ์œ ํœด, ๋‚ด๋ถ€๋Š” ๋ทฐํฌํŠธ
@defer (on idle) {
  <wrapper-component />
}
// wrapper-component ํ…œํ”Œ๋ฆฟ ๋‚ด๋ถ€:
@defer (on viewport) {
  <nested-heavy-component />
}

๋˜ ๋‹ค๋ฅธ ์‹ค์ˆ˜๋Š” ํฌ๋ฆฌํ‹ฐ์ปฌ ๋ Œ๋”๋ง ๊ฒฝ๋กœ์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์— @defer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํด๋“œ ์œ„์˜ ์ฝ˜ํ…์ธ ๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํŽ˜์ธํŒ… ์ „์— ์ฒญํฌ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•ด์•ผ ํ•˜๋ฏ€๋กœ LCP๊ฐ€ ์ฆ๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. @defer๋Š” ํด๋“œ ์•„๋ž˜ ๋˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ์ฝ˜ํ…์ธ ์—๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@defer vs ๋ผ์šฐํ„ฐ ๊ธฐ๋ฐ˜ ์ง€์—ฐ ๋กœ๋”ฉ

๋ผ์šฐํ„ฐ ๊ธฐ๋ฐ˜ ์ง€์—ฐ ๋กœ๋”ฉ๊ณผ @defer๋Š” ์ƒํ˜ธ ๋ณด์™„์ ์ž…๋‹ˆ๋‹ค. ๋ผ์šฐํ„ฐ ์ง€์—ฐ ๋กœ๋”ฉ์€ ๋ผ์šฐํŠธ ์ˆ˜์ค€์—์„œ ๋ถ„ํ• ํ•˜์—ฌ ์ „์ฒด ๊ธฐ๋Šฅ ๋ชจ๋“ˆ์„ ์ง€์—ฐ์‹œํ‚ต๋‹ˆ๋‹ค. @defer๋Š” ํ…œํ”Œ๋ฆฟ ๋‚ด์—์„œ ๋ถ„ํ• ํ•˜์—ฌ ๊ฐœ๋ณ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ง€์—ฐ์‹œํ‚ต๋‹ˆ๋‹ค.

| ๊ด€์  | ๋ผ์šฐํ„ฐ ์ง€์—ฐ ๋กœ๋”ฉ | @defer | |------|-----------------|--------| | ์„ธ๋ถ„์„ฑ | ๋ผ์šฐํŠธ ์ˆ˜์ค€ | ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ค€ | | ํŠธ๋ฆฌ๊ฑฐ | ๋‚ด๋น„๊ฒŒ์ด์…˜ ์ด๋ฒคํŠธ | viewport, idle, interaction, hover, timer, ์กฐ๊ฑด | | SSR ๋™์ž‘ | ์™„์ „ํ•œ ์„œ๋ฒ„ ๋ Œ๋”๋ง | ์„œ๋ฒ„์—์„œ ํ”Œ๋ ˆ์ด์Šคํ™€๋” | | ์Šคํƒ ๋“œ์–ผ๋ก  ํ•„์ˆ˜ | ์•„๋‹ˆ์˜ค (NgModules ์ง€์›) | ์˜ˆ (์Šคํƒ ๋“œ์–ผ๋ก ๋งŒ) | | ํ”„๋ฆฌํŽ˜์น˜ | preloadingStrategy | prefetch on ์ ˆ | | ์‚ฌ์šฉ ์‚ฌ๋ก€ | ๊ธฐ๋Šฅ ๋ชจ๋“ˆ, ํŽ˜์ด์ง€ | ์œ„์ ฏ, ํŒจ๋„, ์กฐ๊ฑด๋ถ€ UI |

์ผ๋ฐ˜์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋‘˜ ๋‹ค ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค: ์ตœ์ƒ์œ„ ๊ธฐ๋Šฅ ์˜์—ญ์—๋Š” ๋ผ์šฐํ„ฐ ์ง€์—ฐ ๋กœ๋”ฉ์„, ํ•ด๋‹น ์˜์—ญ ๋‚ด์˜ ๋ฌด๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ์—๋Š” @defer๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

Angular @defer ๋ฉด์ ‘ ์งˆ๋ฌธ

๊ธฐ์ˆ  ๋ฉด์ ‘์—์„œ @defer๊ฐ€ Angular ์„ฑ๋Šฅ ์ „๋žต์˜ ํ•ต์‹ฌ์ด ๋จ์— ๋”ฐ๋ผ, ์ด ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์งˆ๋ฌธ์ด ์ฆ๊ฐ€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ Angular ๋ฉด์ ‘ ์งˆ๋ฌธ์—์„œ ์ž์ฃผ ๋“ฑ์žฅํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

Q: ๋น„์Šคํƒ ๋“œ์–ผ๋ก  ์ปดํฌ๋„ŒํŠธ๋ฅผ @defer ์•ˆ์— ๋ฐฐ์น˜ํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ฉ๋‹ˆ๊นŒ? ์ปดํฌ๋„ŒํŠธ๋Š” @defer ๋ธ”๋ก๊ณผ ๊ด€๊ณ„์—†์ด ์ฆ‰์‹œ ๋กœ๋”ฉ๋ฉ๋‹ˆ๋‹ค. Angular ์ปดํŒŒ์ผ๋Ÿฌ๋Š” NgModule์— ์„ ์–ธ๋œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ„๋„ ์ฒญํฌ๋กœ ์ถ”์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์˜ค๋ฅ˜๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š์ง€๋งŒ, ์ตœ์ ํ™”๊ฐ€ ์กฐ์šฉํžˆ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

Q: SSR์—์„œ @defer๋Š” ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๊นŒ? ์„œ๋ฒ„๋Š” @placeholder ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ์ง€์—ฐ ์ปดํฌ๋„ŒํŠธ๋Š” ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ๋ Œ๋”๋ง๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ด๋“œ๋ ˆ์ด์…˜์€ ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๋ฐœ๋™๋  ๋•Œ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค(์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜์˜ ๊ฒฝ์šฐ hydrate on ์‚ฌ์šฉ).

Q: @defer๋Š” OnPush ๋ณ€๊ฒฝ ๊ฐ์ง€์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @defer๋Š” ํ…œํ”Œ๋ฆฟ ์ˆ˜์ค€์˜ ๊ธฐ๋Šฅ์ด๋ฉฐ ์–ด๋–ค ๋ณ€๊ฒฝ ๊ฐ์ง€ ์ „๋žต๊ณผ๋„ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค. ์ง€์—ฐ ์ปดํฌ๋„ŒํŠธ๋Š” ๋กœ๋”ฉ๋œ ํ›„ ์ž์ฒด ๋ณ€๊ฒฝ ๊ฐ์ง€ ์„ค์ •์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

Q: on idle๊ณผ on immediate์˜ ์ฐจ์ด์ ์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? on idle์€ requestIdleCallback์„ ํ†ตํ•ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ชจ๋“  ๋ณด๋ฅ˜ ์ค‘์ธ ์ž‘์—…์„ ์™„๋ฃŒํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค. on immediate๋Š” Angular๊ฐ€ ์ดˆ๊ธฐ ๋ Œ๋”๋ง ํŒจ์Šค๋ฅผ ์™„๋ฃŒํ•œ ์งํ›„, ์œ ํœด ์ƒํƒœ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ํŠธ๋ฆฌ๊ฑฐ๋ฉ๋‹ˆ๋‹ค. on immediate๋Š” ์ฒซ ๋ฒˆ์งธ ํŽ˜์ธํŠธ๋ฅผ ์ฐจ๋‹จํ•ด์„œ๋Š” ์•ˆ ๋˜์ง€๋งŒ ๋นจ๋ฆฌ ๋กœ๋”ฉํ•ด์•ผ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

๋” ๋„“์€ ๋ฒ”์œ„์˜ Angular ๋ฉด์ ‘ ์ค€๋น„์— ๋Œ€ํ•ด์„œ๋Š” Angular ๋ณ€๊ฒฝ ๊ฐ์ง€ ๋ชจ๋“ˆ๊ณผ Angular ์‹œ๊ทธ๋„ ๋ชจ๋“ˆ์„ ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ฒฐ๋ก 

  • @defer๋Š” ์Šคํƒ ๋“œ์–ผ๋ก  ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ„๋„์˜ ์ฒญํฌ๋กœ ๋ถ„ํ• ํ•˜์—ฌ ๋ผ์šฐํŠธ ์žฌ๊ตฌ์„ฑ ์—†์ด ์ดˆ๊ธฐ ๋ฒˆ๋“ค ํฌ๊ธฐ๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค
  • 7๊ฐ€์ง€ ํŠธ๋ฆฌ๊ฑฐ ์œ ํ˜•(idle, viewport, interaction, hover, timer, immediate, when)์ด ์ˆ˜๋™์  ๋กœ๋”ฉ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž ์ฃผ๋„ ๋กœ๋”ฉ๊นŒ์ง€ ๋ชจ๋“  ์ „๋žต์„ ํฌ๊ด„ํ•ฉ๋‹ˆ๋‹ค
  • prefetch on์€ ์ฒญํฌ ๋‹ค์šด๋กœ๋“œ์™€ ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์„ ๋ถ„๋ฆฌํ•˜์—ฌ, ์‚ฌ์šฉ์ž ํŠธ๋ฆฌ๊ฑฐ ์ปดํฌ๋„ŒํŠธ์˜ ์ฒด๊ฐ ์ง€์—ฐ ์‹œ๊ฐ„์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค
  • ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜(hydrate on)์€ ์„œ๋ฒ„์—์„œ ์™„์ „ํ•œ HTML์„ ๋ Œ๋”๋งํ•˜๋ฉด์„œ JavaScript ์‹คํ–‰์„ ํด๋ผ์ด์–ธํŠธ๋กœ ์ง€์—ฐ์‹œ์ผœ ์ฝ˜ํ…์ธ ๊ฐ€ ๋งŽ์€ ํŽ˜์ด์ง€์—์„œ 40~50%์˜ LCP ๊ฐœ์„ ์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค
  • @placeholder ์ฝ˜ํ…์ธ ๋Š” SSR ์‹œ ๋ Œ๋”๋ง๋˜๋ฏ€๋กœ, SEO๋ฅผ ์œ„ํ•ด ์‹œ๋งจํ‹ฑ์ ์œผ๋กœ ์˜๋ฏธ ์žˆ๋Š” ๋‚ด์šฉ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
  • @defer๋Š” ๋ผ์šฐํ„ฐ ๊ธฐ๋ฐ˜ ์ง€์—ฐ ๋กœ๋”ฉ์„ ๋ณด์™„ํ•ฉ๋‹ˆ๋‹ค: ๋ผ์šฐํŠธ๋Š” ๊ธฐ๋Šฅ ์ˆ˜์ค€ ๋ถ„ํ• ์—, @defer๋Š” ํ…œํ”Œ๋ฆฟ ๋‚ด ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ค€ ๋ถ„ํ• ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค
  • ๋น„์Šคํƒ ๋“œ์–ผ๋ก  ์ปดํฌ๋„ŒํŠธ๋Š” ์ง€์—ฐ ๋กœ๋”ฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฉฐ ์•”๋ฌต์ ์œผ๋กœ ์ฆ‰์‹œ ๋กœ๋”ฉ๋ฉ๋‹ˆ๋‹ค

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

ํƒœ๊ทธ

#angular
#angular-defer
#lazy-loading
#performance
#ssr
#signals
#deep-dive

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

Angular 19 ๋ฉด์ ‘ ์งˆ๋ฌธ: Signals, SSR, ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜

Angular 19 ๋ฉด์ ‘ ์งˆ๋ฌธ: Signals, SSR๊ณผ ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  ๊ฐœ๋…

๊ฐ€์žฅ ์ž์ฃผ ๋‚˜์˜ค๋Š” Angular 19 ๋ฉด์ ‘ ์งˆ๋ฌธ: Signals, ์ ์ง„์  ํ•˜์ด๋“œ๋ ˆ์ด์…˜, zoneless ๋ณ€๊ฒฝ ๊ฐ์ง€, ์ƒˆ๋กœ์šด ๋ฆฌ์•กํ‹ฐ๋ธŒ API. ์ฝ”๋“œ ์˜ˆ์ œ์™€ ๊ธฐ๋Œ€๋˜๋Š” ๋‹ต๋ณ€๊นŒ์ง€ ํ•จ๊ป˜ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Angular 2026์˜ RxJS ์—ฐ์‚ฐ์ž, Subject, Signals ์ƒํ˜ธ ์šด์šฉ

Angular 2026์˜ RxJS: ์—ฐ์‚ฐ์ž, Subject, Signals ์ƒํ˜ธ ์šด์šฉ

Angular 2026์˜ RxJS: ์‹ค๋ฌด์—์„œ ์“ฐ์ด๋Š” ์—ฐ์‚ฐ์ž, Subject, Signals ์ƒํ˜ธ ์šด์šฉ ํŒจํ„ด์„ ๊ฐ€์žฅ ์ž์ฃผ ๋‚˜์˜ค๋Š” ๋ฉด์ ‘ ์งˆ๋ฌธ๊ณผ ํ•จ๊ป˜ ์ตํž™๋‹ˆ๋‹ค.

์‹œ๊ทธ๋„๊ณผ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” Angular zoneless ๋ณ€๊ฒฝ ๊ฐ์ง€ ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ

Angular 19 Zoneless: Zone.js ์—†์ด ๊ตฌํ˜„ํ•˜๋Š” ์„ฑ๋Šฅ๊ณผ ๋ณ€๊ฒฝ ๊ฐ์ง€

Angular์˜ zoneless ๋ณ€๊ฒฝ ๊ฐ์ง€๋Š” Zone.js๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜์—ฌ ๋” ์ž‘์€ ๋ฒˆ๋“ค ํฌ๊ธฐ, ๋น ๋ฅธ ๋ Œ๋”๋ง, ๊ทธ๋ฆฌ๊ณ  ์‹œ๊ทธ๋„์„ ํ†ตํ•œ ๋ช…์‹œ์  ๋ฐ˜์‘์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ์‹ฌ์ธต ๊ฐ€์ด๋“œ๋Š” Angular 19์˜ provideExperimentalZonelessChangeDetection๋ถ€ํ„ฐ Angular 20+ ์•ˆ์ •ํ™” API๊นŒ์ง€ Zone.js์—์„œ zoneless Angular๋กœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฝ๋กœ๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.