Marketplace performance-vitals
Enforce Core Web Vitals optimization. Use when building user-facing features, reviewing performance, or when Lighthouse scores drop. Covers LCP, FID/INP, CLS, and optimization techniques.
install
source · Clone the upstream repo
git clone https://github.com/aiskillstore/marketplace
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aiskillstore/marketplace "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/doyajin174/performance-vitals" ~/.claude/skills/aiskillstore-marketplace-performance-vitals && rm -rf "$T"
manifest:
skills/doyajin174/performance-vitals/SKILL.mdsource content
Performance & Core Web Vitals
Core Web Vitals 최적화를 강제하는 스킬입니다.
2025 Context
2024년 3월: INP(Interaction to Next Paint)가 FID를 대체 Google Search 랭킹에 Core Web Vitals 영향
Core Web Vitals 목표
| 지표 | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP (Largest Contentful Paint) | ≤ 2.5s | 2.5s - 4s | > 4s |
| INP (Interaction to Next Paint) | ≤ 200ms | 200ms - 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | 0.1 - 0.25 | > 0.25 |
LCP 최적화 (Largest Contentful Paint)
문제 원인
- 느린 서버 응답
- 렌더 차단 리소스 (CSS, JS)
- 느린 리소스 로딩
- 클라이언트 사이드 렌더링
해결 방법
1. 이미지 최적화
// ❌ BAD: 최적화 없는 이미지 <img src="/hero.jpg" alt="Hero" /> // ✅ GOOD: Next.js Image 컴포넌트 import Image from 'next/image'; <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority // LCP 이미지는 priority 추가 placeholder="blur" blurDataURL={blurDataUrl} />
2. 폰트 최적화
// ✅ GOOD: Next.js 폰트 최적화 import { Inter } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], display: 'swap', // FOIT 방지 preload: true, }); // CSS @font-face { font-family: 'Custom Font'; src: url('/fonts/custom.woff2') format('woff2'); font-display: swap; }
3. Critical CSS 인라인
// next.config.js module.exports = { experimental: { optimizeCss: true, }, };
4. 프리로드
<!-- 중요 리소스 프리로드 --> <link rel="preload" href="/hero.jpg" as="image" /> <link rel="preload" href="/fonts/main.woff2" as="font" crossorigin /> <link rel="preconnect" href="https://api.example.com" />
INP 최적화 (Interaction to Next Paint)
문제 원인
- 긴 JavaScript 태스크
- 과도한 DOM 크기
- 비효율적인 이벤트 핸들러
해결 방법
1. 긴 태스크 분할
// ❌ BAD: 긴 동기 작업 function processLargeData(items: Item[]) { items.forEach(item => { heavyComputation(item); // 메인 스레드 블로킹 }); } // ✅ GOOD: 청크 분할 + yield async function processLargeData(items: Item[]) { const CHUNK_SIZE = 100; for (let i = 0; i < items.length; i += CHUNK_SIZE) { const chunk = items.slice(i, i + CHUNK_SIZE); chunk.forEach(item => heavyComputation(item)); // 브라우저에 제어권 반환 await new Promise(resolve => setTimeout(resolve, 0)); } }
2. 이벤트 핸들러 최적화
// ❌ BAD: 무거운 핸들러 <input onChange={(e) => { const value = e.target.value; validateAllFields(); // 무거운 연산 updateUI(); sendAnalytics(); }} /> // ✅ GOOD: 디바운스 + 분리 import { useDeferredValue, useTransition } from 'react'; function SearchInput() { const [input, setInput] = useState(''); const deferredInput = useDeferredValue(input); const [isPending, startTransition] = useTransition(); const handleChange = (e: ChangeEvent<HTMLInputElement>) => { setInput(e.target.value); // 즉시 업데이트 startTransition(() => { // 덜 급한 업데이트는 transition으로 performSearch(e.target.value); }); }; return <input value={input} onChange={handleChange} />; }
3. Web Worker 활용
// worker.ts self.onmessage = (e) => { const result = heavyComputation(e.data); self.postMessage(result); }; // main.ts const worker = new Worker(new URL('./worker.ts', import.meta.url)); worker.postMessage(data); worker.onmessage = (e) => { setResult(e.data); };
CLS 최적화 (Cumulative Layout Shift)
문제 원인
- 크기 미지정 이미지/비디오
- 동적 콘텐츠 삽입
- 웹폰트 FOIT/FOUT
- 애니메이션
해결 방법
1. 이미지/비디오 크기 지정
// ❌ BAD: 크기 미지정 <img src="/photo.jpg" alt="Photo" /> // ✅ GOOD: 크기 명시 <img src="/photo.jpg" alt="Photo" width={800} height={600} /> // ✅ GOOD: aspect-ratio 사용 <div style={{ aspectRatio: '16/9' }}> <img src="/video-thumb.jpg" alt="Video" style={{ width: '100%' }} /> </div>
2. 동적 콘텐츠 공간 확보
// ❌ BAD: 공간 없이 동적 삽입 {isLoaded && <Banner />} // ✅ GOOD: 스켈레톤으로 공간 확보 {isLoaded ? <Banner /> : <BannerSkeleton />} // ✅ GOOD: min-height로 공간 확보 <div style={{ minHeight: '200px' }}> {isLoaded && <DynamicContent />} </div>
3. 폰트 로딩
/* ✅ GOOD: font-display: swap */ @font-face { font-family: 'CustomFont'; src: url('/font.woff2') format('woff2'); font-display: swap; } /* ✅ GOOD: 폴백 폰트 크기 조정 */ @font-face { font-family: 'CustomFont'; src: url('/font.woff2') format('woff2'); font-display: swap; size-adjust: 105%; /* 폴백 폰트와 크기 맞춤 */ }
4. 애니메이션
/* ❌ BAD: 레이아웃 속성 애니메이션 */ .animate { transition: width 0.3s, height 0.3s, margin 0.3s; } /* ✅ GOOD: transform, opacity만 사용 */ .animate { transition: transform 0.3s, opacity 0.3s; }
측정 도구
Lighthouse CI
# .github/workflows/lighthouse.yml name: Lighthouse CI on: [push] jobs: lighthouse: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Lighthouse CI uses: treosh/lighthouse-ci-action@v10 with: urls: | https://example.com/ https://example.com/dashboard budgetPath: ./lighthouse-budget.json uploadArtifacts: true
lighthouse-budget.json
[ { "path": "/*", "timings": [ { "metric": "largest-contentful-paint", "budget": 2500 }, { "metric": "cumulative-layout-shift", "budget": 0.1 }, { "metric": "interaction-to-next-paint", "budget": 200 } ] } ]
web-vitals 라이브러리
import { onCLS, onINP, onLCP } from 'web-vitals'; function sendToAnalytics(metric: Metric) { console.log(metric.name, metric.value); // Analytics 전송 gtag('event', metric.name, { value: metric.delta, metric_id: metric.id, metric_value: metric.value, metric_delta: metric.delta, }); } onCLS(sendToAnalytics); onINP(sendToAnalytics); onLCP(sendToAnalytics);
빠른 최적화 체크리스트
LCP 개선
- LCP 이미지에
또는prioritypreload - 이미지 포맷 최적화 (WebP/AVIF)
- 서버 응답 시간 < 600ms
- Critical CSS 인라인
- 렌더 차단 리소스 제거
INP 개선
- Long Task > 50ms 제거
- 이벤트 핸들러 최적화
- 무거운 연산 Web Worker로 이동
-
,useTransition
활용useDeferredValue - Third-party 스크립트 지연 로딩
CLS 개선
- 모든 이미지/비디오 크기 지정
- 동적 콘텐츠 공간 확보 (스켈레톤)
-
설정font-display: swap - transform/opacity만 애니메이션
Workflow
1. 성능 측정
# Lighthouse CLI npx lighthouse https://example.com --output=json --output-path=./lh-report.json # 또는 Chrome DevTools > Lighthouse 탭
2. 문제 진단
LCP > 2.5s? → Network 탭에서 LCP 리소스 확인 → 이미지 최적화 / 프리로드 적용 INP > 200ms? → Performance 탭에서 Long Task 확인 → 태스크 분할 / Web Worker 적용 CLS > 0.1? → Layout Shift 원인 요소 확인 → 크기 지정 / 스켈레톤 적용
3. 모니터링
프로덕션: - Google Search Console Core Web Vitals - Real User Monitoring (RUM) - web-vitals 라이브러리 CI/CD: - Lighthouse CI - Performance Budget 설정