Claude Code Plugins

Community-maintained marketplace

Feedback

performance-vitals

@Doyajin174/myskills
0
0

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 Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name performance-vitals
description 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.
allowed-tools Read, Glob, Grep, Edit, Write, Bash
license MIT
metadata [object Object]

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 이미지에 priority 또는 preload
  • 이미지 포맷 최적화 (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 설정

References