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

2026๋…„ 4์›” 30์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

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. LCP๊ฐ€ ํžˆ์–ด๋กœ ์ด๋ฏธ์ง€ ๋•Œ๋ฌธ์— ๋А๋ฆฌ๋‹ค. ๊ฐ€์žฅ ๋จผ์ € ํ™•์ธํ•  Next.js Image ์„ค์ •์€ ๋ฌด์—‡์ธ๊ฐ€?

โœ… ์ •๋‹ต: ์‹ค์ œ LCP ์ด๋ฏธ์ง€์— priority ๋˜๋Š” ์ ์ ˆํ•œ preload ์˜๋„๋ฅผ ์ฃผ๊ณ , sizes์™€ ํฌ๊ธฐ ์ •๋ณด๋ฅผ ์ •ํ™•ํžˆ ์ง€์ •ํ–ˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: LCP ์ด๋ฏธ์ง€๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋นจ๋ฆฌ ๋ฐœ๊ฒฌํ•˜๊ณ  ์˜ฌ๋ฐ”๋ฅธ ํฌ๊ธฐ๋กœ ๋‚ด๋ ค๋ฐ›์•„์•ผ ํ•œ๋‹ค. ๋‹จ์ˆœํžˆ ์••์ถ•๋ฅ ๋งŒ ๋‚ฎ์ถ”๋ฉด ํ•ด๊ฒฐ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋ฐ˜๋Œ€๋กœ ๋ชจ๋“  ์ด๋ฏธ์ง€์— ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ฃผ๋ฉด ๋„คํŠธ์›Œํฌ ๊ฒฝ์Ÿ์ด ์‹ฌํ•ด์ ธ ์ง„์งœ ์ค‘์š”ํ•œ ์ด๋ฏธ์ง€๊ฐ€ ๋Šฆ์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.


Q2. ๋ฌด๊ฑฐ์šด ์—๋””ํ„ฐ ๋•Œ๋ฌธ์— ์ดˆ๊ธฐ INP๊ฐ€ ๋‚˜๋น ์กŒ๋‹ค. ์–ด๋–ค ์ „๋žต์ด ์ ์ ˆํ•œ๊ฐ€?

โœ… ์ •๋‹ต: ์ดˆ๊ธฐ ํ™”๋ฉด์— ํ•„์š” ์—†๋Š” ์—๋””ํ„ฐ๋Š” dynamic import๋กœ ๋ถ„๋ฆฌํ•˜๊ณ , ์ƒํ˜ธ์ž‘์šฉ ์ง์ „์— ๋กœ๋“œ๋˜๋„๋ก ์„ค๊ณ„ํ•œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: INP๋Š” ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์— ํŽ˜์ด์ง€๊ฐ€ ์–ผ๋งˆ๋‚˜ ๋นจ๋ฆฌ ๋ฐ˜์‘ํ•˜๋Š”์ง€๋ฅผ ๋ณธ๋‹ค. ์‚ฌ์šฉํ•˜์ง€๋„ ์•Š์€ ์—๋””ํ„ฐ JS๋ฅผ ์ฒซ ๋กœ๋“œ์— ๋ชจ๋‘ ํ•˜์ด๋“œ๋ ˆ์ด์…˜ํ•˜๋ฉด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ง‰ํžŒ๋‹ค. ์ฝ”๋“œ ์Šคํ”Œ๋ฆฌํŒ…, ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๊ฒฝ๊ณ„ ์ถ•์†Œ, ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋‹จ์ˆœํ™”๊ฐ€ ํ•จ๊ป˜ ํ•„์š”ํ•˜๋‹ค.


Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ๋ฐฐ๋„ˆ๊ฐ€ ๋Šฆ๊ฒŒ ๋กœ๋“œ๋˜๋ฉฐ ๊ธ€ ๋ชฉ๋ก์ด ์•„๋ž˜๋กœ ๋ฐ€๋ฆฐ๋‹ค. CLS๋ฅผ ์ค„์ด๋ ค๋ฉด ๋ฌด์—‡์„ ํ•ด์•ผ ํ• ๊นŒ?

โœ… ์ •๋‹ต: ์ด๋ฏธ์ง€์™€ ๊ด‘๊ณ  ์˜์—ญ์˜ width/height ๋˜๋Š” aspect-ratio๋ฅผ ๋ฏธ๋ฆฌ ์˜ˆ์•ฝํ•˜๊ณ , fallback๋„ ๊ฐ™์€ ๊ณต๊ฐ„์„ ์ฐจ์ง€ํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค: CLS๋Š” ๋’ค๋Šฆ๊ฒŒ ์ƒ๊ธด ์š”์†Œ๊ฐ€ ๊ธฐ์กด ๋ ˆ์ด์•„์›ƒ์„ ๋ฐ€์–ด๋‚ผ ๋•Œ ์•…ํ™”๋œ๋‹ค. ๋ฐ์ดํ„ฐ๊ฐ€ ๋นจ๋ฆฌ ์˜ค๊ฒŒ ํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•˜๋‹ค. ์Šค์ผˆ๋ ˆํ†ค, ์ด๋ฏธ์ง€ ํฌ๊ธฐ, ํฐํŠธ ๋กœ๋”ฉ ์ „๋žต์ฒ˜๋Ÿผ "๊ณต๊ฐ„์„ ๋จผ์ € ์•ฝ์†ํ•˜๋Š” ์„ค๊ณ„"๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

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

์˜ค๋Š˜์€ ์„ฑ๋Šฅ์„ Lighthouse ์ ์ˆ˜ ํ•˜๋‚˜๋กœ๋งŒ ๋ณด์ง€ ์•Š๊ธฐ๋กœ ํ–ˆ๋‹ค. LCP๋Š” ๋ฐœ๊ฒฌ๊ณผ ์ „์†ก, INP๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์™€ ํ•˜์ด๋“œ๋ ˆ์ด์…˜, CLS๋Š” ๊ณต๊ฐ„ ์˜ˆ์•ฝ ๋ฌธ์ œ๋ผ๋Š” ์‹์œผ๋กœ ์›์ธ์„ ๋‚˜๋ˆ ์•ผ ํ•ด๊ฒฐ์ฑ…๋„ ์ •ํ™•ํ•ด์กŒ๋‹ค.

๐Ÿ’ก "์„ฑ๋Šฅ ๊ฐœ์„ ์€ ์ˆซ์ž๋ฅผ ๋‚ฎ์ถ”๋Š” ์ผ์ด ์•„๋‹ˆ๋ผ ๋ณ‘๋ชฉ์˜ ์ข…๋ฅ˜๋ฅผ ๋งžํžˆ๋Š” ์ผ์ด๋‹ค."

๋‹ค์Œ PR์—์„œ ์ด๋ฏธ์ง€๊ฐ€ ๋ณด์ด๋ฉด ํฌ๊ธฐ์™€ sizes๋ฅผ, ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ณด์ด๋ฉด ์ดˆ๊ธฐ JS ํ•„์š”์„ฑ์„, ์Šค์ผˆ๋ ˆํ†ค์ด ๋ณด์ด๋ฉด ์‹ค์ œ ์ฝ˜ํ…์ธ ์™€ ๊ฐ™์€ ๊ณต๊ฐ„์„ ์ฐจ์ง€ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ฒ ๋‹ค. ์ด์ œ "๋А๋ ค์š”"๋ผ๋Š” ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์œผ๋ฉด ๋จผ์ € ์–ด๋–ค Core Web Vitals๊ฐ€ ํ”๋“ค๋ฆฌ๋Š”์ง€๋ถ€ํ„ฐ ๋ฌผ์–ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.

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