๐ Next.js ์ฌํ 8์ฅ: Performance Deep Dive โ Core Web Vitals ์ค์ ํ๋
๐ ๊ฐ์
Core Web Vitals ์ค์ ํ๋ โ LCP, CLS, INP๋ฅผ ๊ฐ์ ํ๋ ๊ตฌ์ฒด์ ์ธ ๊ธฐ๋ฒ์ ๋ค๋ฃน๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐ Core Web Vitals โ ์ธ ๊ฐ์ง ์ ์ํ ๐ข
- โก LCP ํ๋ โ ๊ฐ์ฅ ํฐ ์์๋ฅผ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๐ก
- ๐ฑ๏ธ INP ํ๋ โ ํด๋ฆญ ๋ฐ์์ 50ms ์ด๋ด๋ก ๐ก
- ๐ CLS ํ๋ โ ๋ ์ด์์ ์ด๋ 0์ผ๋ก ๋ง๋ค๊ธฐ ๐ก
- ๐ฆ ๋ฒ๋ค ์ต์ ํ โ ํ์ํ ๊ฒ๋ง ๋ด๋ ค๋ณด๋ด๊ธฐ ๐ด
- ๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 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 ๊ธฐ์ค๊ฐ์ ์๋ค
| ์งํ | ์๋ฏธ | Good | Poor |
|---|---|---|---|
| LCP | Largest Contentful Paint โ ๋ทฐํฌํธ์ ๊ฐ์ฅ ํฐ ์์ ๋ ๋ ์๋ฃ | โค 2.5s | > 4s |
| INP | Interaction to Next Paint โ ํด๋ฆญยทํค ์ ๋ ฅ ํ ์๊ฐ์ ์๋ต | โค 200ms | > 500ms |
| CLS | Cumulative 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๋ฅผ ๋๋ฆฌ๊ฒ ๋ง๋๋ ์ฃผ๋ฒ:
- ํ์ด๋ก ์ด๋ฏธ์ง์
priority์์ โ Lazy Loading์ผ๋ก ๋ฆ๊ฒ ๋ก๋ - TTFB(์๋ฒ ์๋ต) ๋๋ฆผ โ Dynamic Rendering์ธ๋ฐ DB ์ฟผ๋ฆฌ๊ฐ ๋๋ฆผ
- ํฐํธ ๋ก๋ ์ง์ฐ โ 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๋ฅผ ๋์๊ฒ ๋ง๋๋ ์ฃผ๋ฒ:
- ํฐ JS ๋ฒ๋ค โ Hydration์ ์ค๋ ๊ฑธ๋ฆผ
- Long Task (50ms ์ด์ ์คํ๋๋ JS)
- ์ด๋ฒคํธ ํธ๋ค๋ฌ์์ ๋ฌด๊ฑฐ์ด ๋๊ธฐ ์ฐ์ฐ
// โ
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๊ฐ ์ด๋ฏธ์ง์ธ๋ฐ ์ ์๊ฐ ๋ฎ์
์ฒดํฌ๋ฆฌ์คํธ:
<Image>์ปดํฌ๋ํธ ์ฌ์ฉ ์ค์ธ๊ฐ?- LCP ์ด๋ฏธ์ง์
priority์์ฑ ์๋๊ฐ? sizes์์ฑ์ด ์ ํํ๊ฐ?- ์ด๋ฏธ์ง๊ฐ CDN์ ์๋๊ฐ (์๋ต ์๋)?
โ window is not defined โ SSR ์ค๋ฅ
์์ธ: ํด๋ผ์ด์ธํธ ์ ์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ SSR์์ ์คํ.
ํด๊ฒฐ์ฑ :
const HeavyLibrary = dynamic(() => import('@/components/HeavyLibrary'), {
ssr: false, // SSR ๋นํ์ฑํ
})๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
๐ ์งํ๋ณ ์ต์ ํ ์์ฝ
| ์งํ | ์์ธ | ํด๊ฒฐ์ฑ |
|---|---|---|
| LCP ๋๋ฆผ | ํ์ด๋ก ์ด๋ฏธ์ง lazy load | priority + sizes |
| LCP ๋๋ฆผ | TTFB ๋๋ฆผ | ISR ๋๋ PPR ์ ํ |
| INP ๋์จ | ํฐ JS ๋ฒ๋ค | next/dynamic ์ฝ๋ ์คํ๋ฆฌํ
|
| INP ๋์จ | Long Task | scheduler.yield() |
| CLS ๋ฐ์ | ์ด๋ฏธ์ง ํฌ๊ธฐ ๋ฏธ์ง์ | width/height ๋ช
์ |
| CLS ๋ฐ์ | ํฐํธ ๊ต์ฒด | next/font + adjustFontFallback |
โ ๏ธ ์ ๋ ํ์ง ๋ง ๊ฒ
| ์ํฉ | โ ๋์ ์ | โ ์ข์ ์ |
|---|---|---|
| ๋ชจ๋ ์ด๋ฏธ์ง์ priority | ์ ๋ถ ์ฆ์ ๋ก๋ โ ๊ฒฝํฉ | LCP ์ด๋ฏธ์ง ํ๋๋ง |
| lodash ์ ์ฒด import | import _ 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๊ฐ ํ๋ค๋ฆฌ๋์ง๋ถํฐ ๋ฌผ์ด๋ณผ ์ ์๋ค.