← 홈으로 돌아가기

📊 성능 분석 & 최적화

Lighthouse, Web Vitals, Bundle Analyzer를 활용한 성능 모니터링

✨ 성능 분석의 3가지 축

🚦 Lighthouse

  • • 종합 성능 점수
  • • 접근성, SEO 평가
  • • 개선 권장사항
  • • CI/CD 통합 가능

🔬 Web Vitals

  • • 실사용자 경험 측정
  • • LCP, FID, CLS
  • • 실시간 모니터링
  • • Google 검색 순위

📦 Bundle Analyzer

  • • 번들 크기 시각화
  • • 불필요한 의존성 발견
  • • Code splitting 최적화
  • • Tree shaking 확인

🔬 Web Vitals 실시간 모니터링

성능 데이터 수집 중...

📦 패키지 설치

npm install web-vitals

🚦 Lighthouse 성능 점수

92
Performance
95
Accessibility
87
Best Practices
100
SEO

💡 Lighthouse 실행 방법

1. Chrome DevTools → Lighthouse 탭
2. 측정 항목 선택 (Performance, Accessibility 등)
3. "Analyze page load" 클릭
# CLI로 실행
npm install -g lighthouse
lighthouse https://your-site.com --view

📦 Bundle Size Analysis

245KB
850KB
120KB
45KB
Total: 1260KB
main.js
19.4%245KB
vendor.js
67.5%850KB
chunks/*.js
9.5%120KB
styles.css
3.6%45KB

⚙️ Bundle Analyzer 설정

1. 패키지 설치

npm install -D @next/bundle-analyzer

2. next.config.js 설정

const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer({
  // your next config
})

3. 분석 실행

ANALYZE=true npm run build

1️⃣ Web Vitals 측정 코드

// app/layout.tsx
import { Suspense } from 'react';
import { WebVitals } from './web-vitals';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Suspense>
          <WebVitals />
        </Suspense>
      </body>
    </html>
  );
}

// web-vitals.tsx
'use client';

import { useReportWebVitals } from 'next/web-vitals';

export function WebVitals() {
  useReportWebVitals((metric) => {
    console.log(metric);
    
    // Analytics로 전송
    if (window.gtag) {
      window.gtag('event', metric.name, {
        value: Math.round(
          metric.name === 'CLS' ? metric.value * 1000 : metric.value
        ),
        event_label: metric.id,
        non_interaction: true,
      });
    }
    
    // 커스텀 API로 전송
    fetch('/api/analytics', {
      method: 'POST',
      body: JSON.stringify({
        name: metric.name,
        value: metric.value,
        rating: metric.rating,
        delta: metric.delta,
        id: metric.id,
      }),
    });
  });
  
  return null;
}

// 또는 web-vitals 패키지 직접 사용
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  const url = '/api/analytics';
  
  // Navigator.sendBeacon 사용 (페이지 종료 시에도 전송 보장)
  if (navigator.sendBeacon) {
    navigator.sendBeacon(url, body);
  } else {
    fetch(url, { body, method: 'POST', keepalive: true });
  }
}

onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);

2️⃣ Lighthouse CI 설정

// .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Run Lighthouse CI
        run: |
          npm install -g @lhci/cli
          lhci autorun
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      staticDistDir: './out',
      url: [
        'http://localhost:3000/',
        'http://localhost:3000/about',
      ],
      numberOfRuns: 3,
    },
    assert: {
      preset: 'lighthouse:recommended',
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'categories:accessibility': ['error', { minScore: 0.95 }],
        'categories:best-practices': ['error', { minScore: 0.9 }],
        'categories:seo': ['error', { minScore: 0.95 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

3️⃣ Performance API 활용

// 커스텀 성능 측정
function measurePerformance() {
  // 1. Navigation Timing
  const perfData = performance.getEntriesByType('navigation')[0];
  console.log('DNS Lookup:', perfData.domainLookupEnd - perfData.domainLookupStart);
  console.log('TCP Connection:', perfData.connectEnd - perfData.connectStart);
  console.log('Time to First Byte:', perfData.responseStart - perfData.requestStart);
  console.log('DOM Interactive:', perfData.domInteractive - perfData.fetchStart);
  console.log('DOM Complete:', perfData.domComplete - perfData.fetchStart);
  console.log('Load Complete:', perfData.loadEventEnd - perfData.fetchStart);
  
  // 2. Resource Timing
  const resources = performance.getEntriesByType('resource');
  resources.forEach((resource) => {
    console.log(`${resource.name}: ${resource.duration}ms`);
  });
  
  // 3. User Timing (커스텀 측정)
  performance.mark('api-fetch-start');
  
  fetch('/api/data')
    .then(response => response.json())
    .then(data => {
      performance.mark('api-fetch-end');
      performance.measure('api-fetch', 'api-fetch-start', 'api-fetch-end');
      
      const measure = performance.getEntriesByName('api-fetch')[0];
      console.log('API Fetch Time:', measure.duration);
    });
  
  // 4. Long Tasks 감지
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      console.warn('Long Task detected:', entry.duration);
      // 50ms 이상 걸리는 작업 추적
      if (entry.duration > 50) {
        // Analytics로 전송
      }
    }
  });
  
  observer.observe({ entryTypes: ['longtask'] });
  
  // 5. Layout Shift 감지
  const layoutShiftObserver = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (!entry.hadRecentInput) {
        console.log('Layout Shift:', entry.value);
      }
    }
  });
  
  layoutShiftObserver.observe({ entryTypes: ['layout-shift'] });
}

// React 컴포넌트에서 사용
function MyComponent() {
  useEffect(() => {
    const startTime = performance.now();
    
    return () => {
      const endTime = performance.now();
      console.log('Component lifetime:', endTime - startTime);
    };
  }, []);
  
  return <div>...</div>;
}

✅ 성능 최적화 체크리스트

번들 최적화

  • Dynamic Import로 Code Splitting
  • Tree Shaking 활용
  • 불필요한 라이브러리 제거
  • Minification & Compression

이미지 최적화

  • Next.js Image 컴포넌트 사용
  • WebP/AVIF 포맷 사용
  • Lazy Loading 적용
  • 적절한 크기와 해상도

렌더링 최적화

  • React.memo로 불필요한 리렌더링 방지
  • useMemo, useCallback 활용
  • Virtualization (react-window)
  • Suspense & Streaming SSR

네트워크 최적화

  • CDN 사용
  • HTTP/2 & HTTP/3
  • Preload & Prefetch
  • 캐싱 전략 (SWR, React Query)

🎯 성능 목표 기준

Web Vitals 목표

  • LCP (Largest Contentful Paint)< 2.5s
  • FID (First Input Delay)< 100ms
  • CLS (Cumulative Layout Shift)< 0.1

Bundle Size 목표

  • First Load JS< 200KB
  • Page JS< 50KB
  • Total Size< 1MB