๐Ÿš€ Next.js ์‹ฌํ™” 8์žฅ: Performance Deep Dive โ€” Core Web Vitals ์‹ค์ „ ํŠœ๋‹

๐Ÿ“‹ ๊ฐœ์š”

Core Web Vitals ์‹ค์ „ ํŠœ๋‹ โ€” LCP, CLS, INP๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๊ตฌ์ฒด์ ์ธ ๊ธฐ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 18๋ถ„ (์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 9๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ์˜์ฒ (์‹ ์ž…): "Lighthouse ์ ์ˆ˜๊ฐ€ 47์ด์—์š”. ๊ธฐ์ˆ  PM์ธ ์˜์ˆ˜ ์”จ๊ฐ€ 'Performance ์ ์ˆ˜ 90 ์ด์ƒ ์˜ฌ๋ ค์•ผ ํ•œ๋‹ค'๊ณ  ํ–ˆ๋Š”๋ฐ, ๋ญ˜ ์–ด๋–ป๊ฒŒ ๊ฑด๋“œ๋ ค์•ผ ํ•˜๋Š”์ง€ ๋ชจ๋ฅด๊ฒ ์–ด์š”. ์ผ๋‹จ ์ด๋ฏธ์ง€ ์••์ถ•ํ•˜๋ฉด ๋˜๋‚˜์š”?"
  • ์˜ํ˜ธ(๋ฆฌ๋“œ): "์ด๋ฏธ์ง€ ์••์ถ•์€ ์‹œ์ž‘์ด์—์š”. Core Web Vitals ์„ธ ๊ฐ€์ง€(LCP, INP, CLS)๋ฅผ ๊ฐ๊ฐ ์ง„๋‹จํ•˜๊ณ , ์›์ธ์— ๋งž๋Š” ์ตœ์ ํ™”๋ฅผ ํ•ด์•ผ ํ•ด์š”. Lighthouse๊ฐ€ ๊ฐ ์ง€ํ‘œ์˜ ์ ์ˆ˜๋ฅผ ์•Œ๋ ค์ฃผ๋‹ˆ๊นŒ ๋‚ฎ์€ ๊ฒƒ๋ถ€ํ„ฐ ์ˆœ์„œ๋Œ€๋กœ ๊ณต๋žตํ•ด๋ด์š”."

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
Core Web Vitals ๊ฐœ๋… โ†’ LCP ์ตœ์ ํ™” โ†’ INP ์ตœ์ ํ™” โ†’ CLS ์ตœ์ ํ™” โ†’ ๋ฒˆ๋“ค ์ตœ์ ํ™”

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • Lighthouse ๊ฒฐ๊ณผ์—์„œ LCP, INP, CLS ์ ์ˆ˜๋ฅผ ๋ณด๊ณ  ๊ฐ๊ฐ์˜ ์›์ธ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ๊ฐ ์ง€ํ‘œ๋ณ„ ์ตœ์ ํ™” ๊ธฐ๋ฒ•์„ ์‹ค์ œ ์ฝ”๋“œ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค
  • next/dynamic์œผ๋กœ ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…์„ ์ ์šฉํ•ด ์ดˆ๊ธฐ ๋ฒˆ๋“ค ํฌ๊ธฐ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

์„ฑ๋Šฅ์€ ์ธก์ • ๊ฐ€๋Šฅํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ์ง€ํ‘œ์•ผ. ๊ฐ์ด ์•„๋‹ˆ์•ผ:

์ง€ํ‘œ ๊ฐœ์„ ๋น„์ฆˆ๋‹ˆ์Šค ์˜ํ–ฅ
LCP 1์ดˆ ๊ฐœ์„ ์ดํƒˆ๋ฅ  35% ๊ฐ์†Œ
INP 200ms ์ดํ•˜ ๋‹ฌ์„ฑ์ „ํ™˜์œจ 25% ์ฆ๊ฐ€
CLS 0.1 ์ดํ•˜ ๋‹ฌ์„ฑ์ž˜๋ชป๋œ ํด๋ฆญ์œผ๋กœ ์ธํ•œ ์ดํƒˆ ๊ฐ์†Œ

๊ตฌ๊ธ€์€ Core Web Vitals๋ฅผ ๊ฒ€์ƒ‰ ๋žญํ‚น ์‹ ํ˜ธ๋กœ ์‚ฌ์šฉํ•ด. ๋А๋ฆฐ ์‚ฌ์ดํŠธ๋Š” ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์—์„œ ๋ฐ€๋ฆฐ๋‹ค๋Š” ๋œป์ด์•ผ.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?
์Œ์‹์  ์„œ๋น„์Šค ํ’ˆ์งˆ์„ ์„ธ ๊ฐ€์ง€๋กœ ํ‰๊ฐ€ํ•ด๋ด:

  • LCP (์ฒซ ์Œ์‹์ด ๋‚˜์˜ค๋Š” ์†๋„): ์ฃผ๋ฌธํ•˜๊ณ  ์ฒซ ์Œ์‹๊นŒ์ง€ 2.5์ดˆ ์ด๋‚ด
  • INP (์ข…์—…์› ๋ฐ˜์‘ ์†๋„): ์† ๋“ค๋ฉด 200ms ์ด๋‚ด ๋ˆˆ๋งž์ถค
  • CLS (์ƒ์ฐจ๋ฆผ ์•ˆ์ •์„ฑ): ์Œ์‹ ๊ฐ€์ ธ๋‹ค ๋†“์„ ๋•Œ ๋‹ค๋ฅธ ๊ทธ๋ฆ‡์ด ๋ฐ€๋ ค๋‚˜์ง€ ์•Š์Œ

์„ธ ๊ฐ€์ง€ ๋‹ค ์ข‹์•„์•ผ "์ข‹์€ ์Œ์‹์ "์ด์•ผ. ์Œ์‹์ด ๋ง›์žˆ์–ด๋„(๊ธฐ๋Šฅ) ์„œ๋น„์Šค๊ฐ€ ๋‚˜์˜๋ฉด ์†๋‹˜์ด ์•ˆ ์™€(์ดํƒˆ๋ฅ  ์ฆ๊ฐ€).


๐Ÿ“Š Core Web Vitals โ€” ์„ธ ๊ฐ€์ง€ ์ ์ˆ˜ํŒ ๐ŸŸข

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • LCP, INP, CLS๊ฐ€ ๊ฐ๊ฐ ๋ฌด์—‡์„ ์ธก์ •ํ•˜๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ๊ฐ ์ง€ํ‘œ์˜ Good/Needs Improvement/Poor ๊ธฐ์ค€๊ฐ’์„ ์•ˆ๋‹ค
์ง€ํ‘œ์˜๋ฏธGoodPoor
LCPLargest Contentful Paint โ€” ๋ทฐํฌํŠธ์˜ ๊ฐ€์žฅ ํฐ ์š”์†Œ ๋ Œ๋” ์™„๋ฃŒโ‰ค 2.5s> 4s
INPInteraction to Next Paint โ€” ํด๋ฆญยทํ‚ค ์ž…๋ ฅ ํ›„ ์‹œ๊ฐ์  ์‘๋‹ตโ‰ค 200ms> 500ms
CLSCumulative Layout Shift โ€” ๋ ˆ์ด์•„์›ƒ์ด ์˜ˆ์ƒ์น˜ ์•Š๊ฒŒ ์ด๋™ํ•œ ์ด๋Ÿ‰โ‰ค 0.1> 0.25

์ง„๋‹จ ๋„๊ตฌ:

  • Lighthouse (Chrome DevTools) โ€” ๋กœ์ปฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
  • Chrome DevTools Performance ํƒญ โ€” ์‹ค์ œ ๋ Œ๋”๋ง ํƒ€์ž„๋ผ์ธ
  • Vercel Speed Insights โ€” ์‹ค์ œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ(RUM)

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
LCP๋Š” "์ฒซ์ธ์ƒ", INP๋Š” "๋ฐ˜์‘์„ฑ", CLS๋Š” "์•ˆ์ •๊ฐ". ์„ธ ๊ฐ€์ง€ ๋ชจ๋‘ ์‚ฌ์šฉ์ž ์ฒด๊ฐ์— ์ง๊ฒฐ๋ผ.


โšก LCP ํŠœ๋‹ โ€” ๊ฐ€์žฅ ํฐ ์š”์†Œ๋ฅผ ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • LCP ์š”์†Œ๋ฅผ ์‹๋ณ„ํ•˜๊ณ  priority, preload๋กœ ์šฐ์„  ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค
  • ๋ Œ๋”๋ง ์ „๋žต์œผ๋กœ LCP๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•ˆ๋‹ค

LCP๋ฅผ ๋А๋ฆฌ๊ฒŒ ๋งŒ๋“œ๋Š” ์ฃผ๋ฒ”:

  1. ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€์— priority ์—†์Œ โ†’ Lazy Loading์œผ๋กœ ๋Šฆ๊ฒŒ ๋กœ๋“œ
  2. TTFB(์„œ๋ฒ„ ์‘๋‹ต) ๋А๋ฆผ โ†’ Dynamic Rendering์ธ๋ฐ DB ์ฟผ๋ฆฌ๊ฐ€ ๋А๋ฆผ
  3. ํฐํŠธ ๋กœ๋“œ ์ง€์—ฐ โ†’ Google Fonts ์™ธ๋ถ€ ์š”์ฒญ
// โœ… LCP ์ด๋ฏธ์ง€ ์ตœ์ ํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ
 
// 1. priority ์ถ”๊ฐ€ (preload ์ฒ˜๋ฆฌ)
<Image
  src="/hero.jpg"
  alt="ํžˆ์–ด๋กœ ๋ฐฐ๋„ˆ"
  width={1200}
  height={630}
  priority              // โ† ์ ˆ๋Œ€ ๋น ๋œจ๋ฆฌ๋ฉด ์•ˆ ๋จ
  sizes="(max-width: 768px) 100vw, 1200px"
/>
 
// 2. fetchPriority="high" (Next.js 16+)
<Image
  src="/hero.jpg"
  fetchPriority="high"  // ๋ธŒ๋ผ์šฐ์ €์— ๋ช…์‹œ์ ์œผ๋กœ ์šฐ์„ ์ˆœ์œ„ ํžŒํŠธ
  // ...
/>

์„œ๋ฒ„ ์‘๋‹ต(TTFB) ๊ฐœ์„ :

// โœ… DB ์ฟผ๋ฆฌ๊ฐ€ LCP๋ฅผ ๋Šฆ์ถ”๋Š” ๊ฒฝ์šฐ โ€” ISR ๋˜๋Š” PPR๋กœ ์ „ํ™˜
 
// Dynamic Rendering โ†’ ISR๋กœ ๋ฐ”๊ฟ” TTFB ๊ฐœ์„ 
export const revalidate = 60  // 60์ดˆ๋งˆ๋‹ค ์žฌ์ƒ์„ฑ
 
// ๋˜๋Š” PPR๋กœ ์ •์  ์…ธ์„ ๋จผ์ € ๋ณด๋‚ด๊ณ  DB ๋ฐ์ดํ„ฐ๋Š” ์ŠคํŠธ๋ฆฌ๋ฐ
export const experimental_ppr = true
// Suspense๋กœ DB ์กฐํšŒํ•˜๋Š” ๋ถ€๋ถ„๋งŒ ๊ฐ์‹ธ๊ธฐ

๐Ÿ–ฑ๏ธ INP ํŠœ๋‹ โ€” ํด๋ฆญ ๋ฐ˜์‘์„ 50ms ์ด๋‚ด๋กœ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • INP๊ฐ€ ๋‚˜์œ ์›์ธ์„ ํŒŒ์•…ํ•˜๊ณ  Long Task๋ฅผ ๋ถ„ํ• ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•ˆ๋‹ค
  • next/dynamic์œผ๋กœ ํด๋ผ์ด์–ธํŠธ JS๋ฅผ ์ง€์—ฐ ๋กœ๋“œํ•ด ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ๋น„์šฉ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค

INP๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ธ”๋กœํ‚น๋˜๋ฉด ๋‚˜๋น ์ ธ. JS๊ฐ€ ์‹คํ–‰ ์ค‘์ด๋ฉด ํด๋ฆญ์— ๋ฐ˜์‘์„ ๋ชปํ•˜๊ฑฐ๋“ .

INP๋ฅผ ๋‚˜์˜๊ฒŒ ๋งŒ๋“œ๋Š” ์ฃผ๋ฒ”:

  1. ํฐ JS ๋ฒˆ๋“ค โ†’ Hydration์— ์˜ค๋ž˜ ๊ฑธ๋ฆผ
  2. Long Task (50ms ์ด์ƒ ์‹คํ–‰๋˜๋Š” JS)
  3. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ ๋ฌด๊ฑฐ์šด ๋™๊ธฐ ์—ฐ์‚ฐ
// โœ… 1. next/dynamic์œผ๋กœ ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ… โ€” ์ดˆ๊ธฐ ๋ฒˆ๋“ค์—์„œ ์ œ์™ธ
import dynamic from 'next/dynamic'
 
// ์—๋””ํ„ฐ, ์ฐจํŠธ ๊ฐ™์€ ๋ฌด๊ฑฐ์šด ์ปดํฌ๋„ŒํŠธ๋Š” ์ง€์—ฐ ๋กœ๋“œ
const RichTextEditor = dynamic(
  () => import('@/components/features/posts/RichTextEditor'),
  {
    loading: () => <div>์—๋””ํ„ฐ ๋กœ๋”ฉ ์ค‘...</div>,
    ssr: false,   // SSR ์ œ์™ธ (๋ธŒ๋ผ์šฐ์ € API ์˜์กด ์ปดํฌ๋„ŒํŠธ)
  }
)
 
// โœ… 2. ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์ „ํ™˜ โ€” ํด๋ผ์ด์–ธํŠธ ๋ฒˆ๋“ค์—์„œ ๋กœ์ง ์ œ๊ฑฐ
// ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณตํ•˜๋Š” ๋กœ์ง์„ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์ด๋™
// โ†’ Hydration ๋น„์šฉ โ†“ โ†’ INP โ†‘
 
// โœ… 3. scheduler.yield()๋กœ Long Task ๋ถ„ํ• 
async function processLargeList(items: string[]) {
  for (const item of items) {
    await processItem(item)
 
    // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์— ์ž ์‹œ ์–‘๋ณด โ†’ ํด๋ฆญ ์ด๋ฒคํŠธ๊ฐ€ ๋ผ์–ด๋“ค ์ˆ˜ ์žˆ๊ฒŒ ํ•จ
    if (typeof scheduler !== 'undefined') {
      await scheduler.yield()
    }
  }
}

๐Ÿ“ CLS ํŠœ๋‹ โ€” ๋ ˆ์ด์•„์›ƒ ์ด๋™ 0์œผ๋กœ ๋งŒ๋“ค๊ธฐ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • CLS๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์›์ธ 3๊ฐ€์ง€๋ฅผ ์•Œ๊ณ  ๊ฐ๊ฐ์˜ ํ•ด๊ฒฐ์ฑ…์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค

CLS ๋ฐœ์ƒ ์›์ธ๊ณผ ํ•ด๊ฒฐ์ฑ…:

์›์ธ 1: ์ด๋ฏธ์ง€/์˜์ƒ ํฌ๊ธฐ ๋ฏธ์ง€์ •

// โŒ ํฌ๊ธฐ ์—†๋Š” ์ด๋ฏธ์ง€ โ†’ ๋กœ๋“œ ์ „ ๊ณต๊ฐ„ ์—†์Œ โ†’ ๋กœ๋“œ ํ›„ ๋ ˆ์ด์•„์›ƒ ์ด๋™
<img src="/photo.jpg" alt="..." />
 
// โœ… next/image๋กœ ๋ฏธ๋ฆฌ ๊ณต๊ฐ„ ์˜ˆ์•ฝ
<Image src="/photo.jpg" alt="..." width={800} height={400} />

์›์ธ 2: ๋™์ ์œผ๋กœ ์‚ฝ์ž…๋˜๋Š” ์ฝ˜ํ…์ธ  (๊ด‘๊ณ , ๋ฐฐ๋„ˆ)

// โŒ ๋ฐฐ๋„ˆ๊ฐ€ ๋‚˜์ค‘์— ์‚ฝ์ž…๋˜์–ด ์•„๋ž˜ ์ฝ˜ํ…์ธ ๊ฐ€ ๋ฐ€๋ฆผ
<div>  {/* ๋‚˜์ค‘์— ๋ฐฐ๋„ˆ ์‚ฝ์ž… */}  </div>
<main>๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก</main>
 
// โœ… ๋ฐฐ๋„ˆ ์˜์—ญ ๋†’์ด๋ฅผ ๋ฏธ๋ฆฌ ์˜ˆ์•ฝ
<div style={{ minHeight: '90px' }}>  {/* ๊ด‘๊ณ  ๋“ค์–ด์˜ฌ ๊ณต๊ฐ„ ์˜ˆ์•ฝ */}  </div>
<main>๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก</main>

์›์ธ 3: ํฐํŠธ ๊ต์ฒด(FOUT) โ€” ํฐํŠธ ๋กœ๋“œ ์ „ํ›„ ๊ธ€์ž ํฌ๊ธฐ ์ฐจ์ด

// โœ… next/font๋กœ FOUT ๋ฐฉ์ง€ + size-adjust๋กœ ํฌ๊ธฐ ๋งž์ถค
import { Inter } from 'next/font/google'
 
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',   // FOUT ํ—ˆ์šฉํ•˜์ง€๋งŒ layout shift ์ตœ์†Œํ™”
  adjustFontFallback: true,  // ์‹œ์Šคํ…œ ํฐํŠธ์™€ ํฌ๊ธฐ ์ž๋™ ๋งž์ถค โ†’ CLS ๊ฐ์†Œ
})

๐Ÿ“ฆ ๋ฒˆ๋“ค ์ตœ์ ํ™” โ€” ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ๋‚ด๋ ค๋ณด๋‚ด๊ธฐ ๐Ÿ”ด

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • @next/bundle-analyzer๋กœ ๋ฒˆ๋“ค ๊ตฌ์„ฑ์„ ์‹œ๊ฐํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค
  • Tree shaking์ด ์•ˆ ๋˜๋Š” ํŒจํ‚ค์ง€๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ importํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•ˆ๋‹ค
# ๋ฒˆ๋“ค ๋ถ„์„๊ธฐ ์„ค์น˜
npm install @next/bundle-analyzer
// next.config.ts
import withBundleAnalyzer from '@next/bundle-analyzer'
 
const bundleAnalyzer = withBundleAnalyzer({
  enabled: process.env.ANALYZE === 'true',
})
 
export default bundleAnalyzer({
  // ...next config
})
# ๋ฒˆ๋“ค ๋ถ„์„ ์‹คํ–‰ โ†’ ๋ธŒ๋ผ์šฐ์ €์—์„œ treemap ์‹œ๊ฐํ™”
ANALYZE=true npm run build

ํ”ํ•œ ๋ฒˆ๋“ค ์ตœ์ ํ™” ํŒจํ„ด:

// โŒ lodash ์ „์ฒด import โ†’ ๋ฒˆ๋“ค์— ๋‹ค ๋“ค์–ด๊ฐ (70kb+)
import _ from 'lodash'
const result = _.merge(obj1, obj2)
 
// โœ… ํ•„์š”ํ•œ ํ•จ์ˆ˜๋งŒ import โ†’ Tree shaking
import merge from 'lodash/merge'
const result = merge(obj1, obj2)
 
// โœ… ๋˜๋Š” ES ๋ชจ๋“ˆ ๋ฒ„์ „ ์‚ฌ์šฉ
import { merge } from 'lodash-es'
// โŒ moment.js โ€” locale ํŒŒ์ผ๊นŒ์ง€ ๋‹ค ๋“ค์–ด๊ฐ (300kb+)
import moment from 'moment'
 
// โœ… date-fns ๋˜๋Š” dayjs โ€” Tree shaking ์™„๋ฒฝ ์ง€์›
import { format } from 'date-fns/format'
import { ko } from 'date-fns/locale/ko'

next/dynamic์œผ๋กœ ํด๋ผ์ด์–ธํŠธ ๋ฒˆ๋“ค ๋ถ„๋ฆฌ:

// ๋ฌด๊ฑฐ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ง€์—ฐ ๋กœ๋“œ โ†’ ์ดˆ๊ธฐ ๋ฒˆ๋“ค ํฌ๊ธฐ ๊ฐ์†Œ
const ChartComponent = dynamic(() => import('@/components/features/Chart'), {
  ssr: false,           // ์„œ๋ฒ„์—์„œ ๋ Œ๋” ์•ˆ ํ•จ
  loading: () => <ChartSkeleton />,
})

๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ


โŒ Lighthouse LCP๊ฐ€ ์ด๋ฏธ์ง€์ธ๋ฐ ์ ์ˆ˜๊ฐ€ ๋‚ฎ์Œ

์ฒดํฌ๋ฆฌ์ŠคํŠธ:

  1. <Image> ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ ์ค‘์ธ๊ฐ€?
  2. LCP ์ด๋ฏธ์ง€์— priority ์†์„ฑ ์žˆ๋Š”๊ฐ€?
  3. sizes ์†์„ฑ์ด ์ •ํ™•ํ•œ๊ฐ€?
  4. ์ด๋ฏธ์ง€๊ฐ€ CDN์— ์žˆ๋Š”๊ฐ€ (์‘๋‹ต ์†๋„)?

โŒ window is not defined โ€” SSR ์˜ค๋ฅ˜

์›์ธ: ํด๋ผ์ด์–ธํŠธ ์ „์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ SSR์—์„œ ์‹คํ–‰.

ํ•ด๊ฒฐ์ฑ…:

const HeavyLibrary = dynamic(() => import('@/components/HeavyLibrary'), {
  ssr: false,  // SSR ๋น„ํ™œ์„ฑํ™”
})

๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

๐Ÿ“‹ ์ง€ํ‘œ๋ณ„ ์ตœ์ ํ™” ์š”์•ฝ

์ง€ํ‘œ์›์ธํ•ด๊ฒฐ์ฑ…
LCP ๋А๋ฆผํžˆ์–ด๋กœ ์ด๋ฏธ์ง€ lazy loadpriority + sizes
LCP ๋А๋ฆผTTFB ๋А๋ฆผISR ๋˜๋Š” PPR ์ „ํ™˜
INP ๋‚˜์จํฐ JS ๋ฒˆ๋“คnext/dynamic ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…
INP ๋‚˜์จLong Taskscheduler.yield()
CLS ๋ฐœ์ƒ์ด๋ฏธ์ง€ ํฌ๊ธฐ ๋ฏธ์ง€์ •width/height ๋ช…์‹œ
CLS ๋ฐœ์ƒํฐํŠธ ๊ต์ฒดnext/font + adjustFontFallback

โš ๏ธ ์ ˆ๋Œ€ ํ•˜์ง€ ๋ง ๊ฒƒ

์ƒํ™ฉโŒ ๋‚˜์œ ์˜ˆโœ… ์ข‹์€ ์˜ˆ
๋ชจ๋“  ์ด๋ฏธ์ง€์— priority์ „๋ถ€ ์ฆ‰์‹œ ๋กœ๋“œ โ†’ ๊ฒฝํ•ฉLCP ์ด๋ฏธ์ง€ ํ•˜๋‚˜๋งŒ
lodash ์ „์ฒด importimport _ from 'lodash'import merge from 'lodash/merge'
๋ฒˆ๋“ค ํ™•์ธ ์•ˆ ํ•จ๊ฐ์œผ๋กœ ์ตœ์ ํ™”Bundle Analyzer๋กœ ์‹œ๊ฐํ™” ํ›„ ๊ฒฐ์ •

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. INP(Interaction to Next Paint) ์ ์ˆ˜๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์žฅ ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•์€?

  • A) ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€์— priority ์†์„ฑ ์ถ”๊ฐ€
  • B) ์ด๋ฏธ์ง€์— width/height ์†์„ฑ ์ถ”๊ฐ€
  • C) ๋ฌด๊ฑฐ์šด ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ next/dynamic์œผ๋กœ ์ง€์—ฐ ๋กœ๋“œ
  • D) ํฐํŠธ๋ฅผ next/font๋กœ ๊ต์ฒด

โœ… ์ •๋‹ต: C

ํ•ด์„ค: INP๋Š” ํด๋ฆญ ํ›„ JS ์‘๋‹ต์ด ๋А๋ฆฐ ๋ฌธ์ œ์•ผ. ์ดˆ๊ธฐ ๋ฒˆ๋“ค์„ ์ค„์ด๋ฉด Hydration์ด ๋นจ๋ฆฌ ๋๋‚˜๊ณ  ํด๋ฆญ ์‘๋‹ต์ด ๋นจ๋ผ์ ธ. A, B๋Š” LCP/CLS ๊ด€๋ จ, D๋Š” CLS ๊ด€๋ จ.

๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: INP = JS ๋ฌธ์ œ = ๋ฒˆ๋“ค ์ค„์ด๊ธฐ.


Q2. ์•„๋ž˜ ์ค‘ CLS๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์›์ธ์ด ์•„๋‹Œ ๊ฒƒ์€?

  • A) <img> ํƒœ๊ทธ์— width/height ์—†์Œ
  • B) ํŽ˜์ด์ง€ ๋กœ๋“œ ํ›„ ๊ด‘๊ณ  ๋ฐฐ๋„ˆ๊ฐ€ ๋™์ ์œผ๋กœ ์‚ฝ์ž…๋จ
  • C) next/font ๋Œ€์‹  Google Fonts <link> ํƒœ๊ทธ ์‚ฌ์šฉ
  • D) <Image> ์ปดํฌ๋„ŒํŠธ์— priority ์†์„ฑ ์—†์Œ

โœ… ์ •๋‹ต: D

ํ•ด์„ค: priority ์—†์œผ๋ฉด LCP๊ฐ€ ๋‚˜๋น ์ง€์ง€๋งŒ, CLS์™€๋Š” ๋ฌด๊ด€ํ•ด. CLS๋Š” ๋ ˆ์ด์•„์›ƒ์ด ์˜ˆ์ƒ์น˜ ์•Š๊ฒŒ ์ด๋™ํ•˜๋Š” ๊ฑฐ์•ผ.

๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: CLS = "๋ญ”๊ฐ€๊ฐ€ ๊ฐ‘์ž๊ธฐ ํŠ€์–ด๋‚˜์™€์„œ ๋‚ด์šฉ์ด ๋ฐ€๋ฆฌ๋Š” ๊ฒƒ."


Q3. ์นœ๊ตฌ์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?

Core Web Vitals์˜ ์„ธ ์ง€ํ‘œ๋ฅผ ์Œ์‹์  ์„œ๋น„์Šค์— ๋น—๋Œ€์–ด ์„ค๋ช…ํ•ด๋ด.

์˜ˆ์‹œ ๋‹ต๋ณ€:

"LCP๋Š” ์ฃผ๋ฌธ ํ›„ ์ฒซ ์Œ์‹์ด ๋‚˜์˜ค๋Š” ์†๋„์•ผ. 2.5์ดˆ ์ด๋‚ด๋ฉด Good. INP๋Š” ์† ๋“ค์—ˆ์„ ๋•Œ ์ข…์—…์›์ด ๋ˆˆ๋งž์ถคํ•˜๋Š” ์†๋„์•ผ. 200ms ์ด๋‚ด๋ฉด Good. CLS๋Š” ์Œ์‹ ๊ฐ€์ ธ๋‹ค ๋†“์„ ๋•Œ ๋‹ค๋ฅธ ๊ทธ๋ฆ‡์ด ๋ฐ€๋ฆฌ๋ƒ ์•ˆ ๋ฐ€๋ฆฌ๋ƒ์•ผ. ๊ทธ๋ฆ‡์ด ๊ฐ‘์ž๊ธฐ ์ด๋™ํ•˜๋ฉด 0.25์  ์ด์ƒ์ด๋ผ Poor. ์„ธ ๊ฐœ ๋‹ค ์ข‹์•„์•ผ '๋ณ„์  5์ ์งœ๋ฆฌ ์‹๋‹น'์ด์•ผ."


๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜์€ ์ •๋ง ๋„ฅ์ŠคํŠธ์˜ ์„ฑ๋Šฅ์„ ๊ทนํ•œ์œผ๋กœ ๋Œ์–ด์˜ฌ๋ฆฌ๋Š” 'Performance Deep Dive' ๋ฅผ ๋ฐฐ์šฐ๋ฉด์„œ ์งœ๋ฆฟํ•œ ์พŒ๊ฐ์„ ๋А๋‚€ ๋‚ ์ด์•ผ! ๊ทธ๋™์•ˆ ๋‹จ์ˆœํžˆ "๋‚ด ์ปดํ“จํ„ฐ์—์„  ๋นจ๋ผ์š”" ๋ผ๊ณ ๋งŒ ์ƒ๊ฐํ–ˆ๋Š”๋ฐ, Lighthouse ์ง€ํ‘œ๋ฅผ ๋ณด๋ฉด์„œ LCP, INP, CLS๋ฅผ ํ•˜๋‚˜ํ•˜๋‚˜ ๊ฐœ์„ ํ•ด ๋‚˜๊ฐ€๋Š” ๊ณผ์ •์ด ๋งˆ์น˜ ๊ฒŒ์ž„ ํ€˜์ŠคํŠธ๋ฅผ ๊นจ๋Š” ๊ฒƒ ๊ฐ™์•˜์–ด.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” ๊ฐ์ด ์•„๋‹ˆ๋ผ ์ง€ํ‘œ๋กœ ๋งํ•ด์•ผ ํ•œ๋‹ค. next/image ๋กœ ์šฉ๋Ÿ‰์„ ์ค„์ด๊ณ , next/dynamic ์œผ๋กœ ๋ฒˆ๋“ค์„ ์ชผ๊ฐœ๋ฉฐ, scheduler.yield() ๋กœ ์‚ฌ์šฉ์ž ์‘๋‹ต์„ฑ์„ ๋๊นŒ์ง€ ์‚ฌ์ˆ˜ํ•˜์ž!"

์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด ์Œ์‹์  ์„œ๋น„์Šค ๋น„์œ ๋ฅผ ๋“ค์–ด ์„ค๋ช…ํ•ด ์ฃผ์‹ค ๋•Œ, ๋Œ€๊ธฐ ์‹œ๊ฐ„(LCP) ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ข…์—…์›์˜ ์‘๋‹ต ์†๋„(INP)์™€ ๊ทธ๋ฆ‡์˜ ์•ˆ์ •์„ฑ(CLS)๊นŒ์ง€ ์ฑ™๊ฒจ์•ผ ์ง„์ •ํ•œ ๋ช…ํ’ˆ ์‹๋‹น์ด๋ผ๋Š” ๊ฑธ ๊นจ๋‹ฌ์•˜์–ด. ์˜ค๋Š˜ ๋„ˆ๋ฌด ๋ชฐ์ž…ํ•ด์„œ ์ตœ์ ํ™”ํ–ˆ๋”๋‹ˆ ๋จธ๋ฆฌ๊ฐ€ ํŒฝํŒฝ ๋Œ์•„๊ฐ€๋„ค. ํ‡ด๊ทผ๊ธธ์— ๋‚ด๊ฐ€ ์ข‹์•„ํ•˜๋Š” ๋‹จ๊ณจ ์‹๋‹น์— ๋“ค๋Ÿฌ์„œ '์„ฑ๋Šฅ ์ข‹์€' ๋ง›์žˆ๋Š” ์ €๋… ํ•œ ๋ผ ํ•ด์•ผ์ง€! ๋‚ด์ผ์€ ๋” '๋น›๋ณด๋‹ค ๋น ๋ฅธ' ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ๋  ๊ฑฐ์•ผ! ๐Ÿฃ


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ