웹 개발

Next.js로 초고속 웹사이트 만들기: 성능 최적화 완벽 가이드

Daniel Kim|프론트엔드 리드 개발자
2025-01-20
12분 소요
#Next.js#React#성능 최적화#Web Vitals
Next.js로 초고속 웹사이트 만들기: 성능 최적화 완벽 가이드

웹사이트 로딩 속도는 사용자 경험과 SEO에 직접적 영향을 미칩니다. Google 연구에 따르면 페이지 로딩이 1초 지연되면 전환율이 7% 감소합니다. Next.js는 성능 최적화를 위한 강력한 도구를 기본 제공하며, 올바르게 활용하면 경쟁사 대비 3배 빠른 웹사이트를 구축할 수 있습니다.

Next.js Rendering Strategies SSR Server-Side Rendering Request Time Every request triggers server rendering Fresh data always SSG Static Site Generation Build Time HTML pre-generated at build time Fastest performance ISR Incremental Static Regen Hybrid Static + revalidation after interval Best of both worlds
Next.js rendering strategies comparison

Next.js가 성능 최적화에 강한 이유

Next.js는 React의 강력한 생태계와 서버 사이드 최적화를 결합한 프레임워크입니다.

핵심 성능 기능:

  1. 서버 컴포넌트: 클라이언트로 전송되는 JavaScript 양 최소화
  2. 자동 코드 스플리팅: 페이지별 필요한 코드만 로드
  3. 이미지 최적화: WebP 변환, lazy loading 자동 처리
  4. 폰트 최적화: Layout Shift 제거, 자동 서브셋팅
  5. 스트리밍 SSR: 점진적 페이지 렌더링
  6. 정적 생성 (SSG): 빌드 타임 사전 렌더링
📝 Core Web Vitals 목표치
  • LCP (Largest Contentful Paint): 2.5초 이하
  • FID (First Input Delay): 100ms 이하
  • CLS (Cumulative Layout Shift): 0.1 이하

  • Next.js로 제작된 POEMA 프로젝트는 평균 LCP 1.2초, FID 45ms, CLS 0.02를 기록합니다.


    1. 렌더링 전략 선택하기

    Next.js 16 App Router는 4가지 렌더링 전략을 제공합니다. 페이지 특성에 맞는 전략을 선택해야 합니다.

    Next.js Rendering Strategies SSR Server-Side Rendering Request Time Every request triggers server rendering Fresh data always SSG Static Site Generation Build Time HTML pre-generated at build time Fastest performance ISR Incremental Static Regen Hybrid Static + revalidation after interval Best of both worlds
    Next.js rendering strategies comparison
    전략렌더링 시점사용 사례성능 특성
    SSG (정적 생성)빌드 타임블로그, 마케팅 페이지최고 속도, CDN 캐싱
    ISR (증분 정적 재생성)빌드 + 주기적 재생성제품 목록, 뉴스빠른 속도 + 신선한 데이터
    SSR (서버 사이드 렌더링)요청 시개인화 대시보드SEO + 동적 데이터
    CSR (클라이언트 렌더링)브라우저인터랙티브 도구SEO 불필요한 페이지

    SSG: 최고 성능을 위한 정적 생성

    블로그, 랜딩페이지, 포트폴리오처럼 모든 사용자에게 동일한 콘텐츠를 보여주는 페이지는 SSG가 최적입니다.

    tsx
    // app/blog/[slug]/page.tsx
    export async function generateStaticParams() {
      const posts = await getAllPosts();
      return posts.map((post) => ({
        slug: post.slug,
      }));
    }
    
    export default async function BlogPost({ params }: { params: { slug: string } }) {
      const post = await getPostBySlug(params.slug);
    
      return (
        <article>
          <h1>{post.title}</h1>
          <div dangerouslySetInnerHTML={{ __html: post.content }} />
        </article>
      );
    }

    성능 이점:

    • 빌드 타임에 HTML 사전 생성
    • CDN 엣지에 캐싱 → 전 세계 어디서나 50ms 이내 응답
    • JavaScript 번들 크기 최소화

    ISR: 성능과 신선함의 균형

    제품 목록, 뉴스 피드처럼 주기적 업데이트가 필요하지만 실시간일 필요는 없는 페이지에 적합합니다.

    tsx
    // app/products/page.tsx
    export const revalidate = 3600; // 1시간마다 재생성
    
    export default async function ProductsPage() {
      const products = await fetch('https://api.example.com/products', {
        next: { revalidate: 3600 }
      }).then(res => res.json());
    
      return (
        <div className="grid grid-cols-3 gap-4">
          {products.map((product) => (
            <ProductCard key={product.id} product={product} />
          ))}
        </div>
      );
    }

    작동 원리:

    1. 첫 요청: 캐시된 정적 페이지 즉시 반환
    2. 재검증 시간(revalidate) 경과 후 첫 요청: 여전히 캐시 반환하지만 백그라운드에서 재생성
    3. 재생성 완료: 새로운 정적 페이지로 교체
    💡 ISR vs SSG 선택 기준
  • 데이터 업데이트 주기가 1시간 이상: ISR 사용
  • 데이터가 거의 변하지 않음 (월 1회 미만): SSG 사용
  • 실시간 반영 필수: SSR 사용

  • 2. 이미지 최적화: next/image 마스터하기

    이미지는 평균적으로 웹페이지 용량의 50% 이상을 차지합니다. Next.js의 컴포넌트는 이미지 최적화를 자동화합니다.

    tsx
    import Image from 'next/image';
    
    export default function HeroSection() {
      return (
        <div className="relative h-screen">
          <Image
            src="/hero-background.jpg"
            alt="Hero background"
            fill
            priority // LCP 이미지는 priority 필수
            sizes="100vw"
            className="object-cover"
          />
          <div className="relative z-10">
            <h1>Welcome to POEMA</h1>
          </div>
        </div>
      );
    }

    자동 최적화 기능:

    • 포맷 변환: JPEG → WebP/AVIF (최대 50% 용량 감소)
    • 반응형 이미지: sizes 속성 기반 자동 생성
    • Lazy loading: 뷰포트 진입 시 로드 (Below-the-fold 이미지)
    • Blur placeholder: 로딩 중 블러 이미지 표시

    priority vs lazy loading

    속성사용 시기로딩 방식
    priorityLCP 이미지 (히어로, 로고)즉시 로드, preload 링크 생성
    기본 (lazy)Below-the-fold 이미지뷰포트 진입 시 로드
    ⚠️ 흔한 실수

    히어로 이미지에 priority 설정을 빼먹으면 LCP가 3~5초 지연됩니다. 첫 화면에 보이는 큰 이미지는 반드시 priority 추가하세요.

    외부 이미지 최적화

    외부 URL 이미지도 최적화하려면 next.config.js에 도메인 허용 설정이 필요합니다.

    js
    // next.config.js
    module.exports = {
      images: {
        remotePatterns: [
          {
            protocol: 'https',
            hostname: 'cdn.example.com',
            pathname: '/images/**',
          },
        ],
        formats: ['image/avif', 'image/webp'], // AVIF 우선, WebP 대체
      },
    };

    3. 폰트 최적화: Layout Shift 제거

    웹 폰트는 CLS(Cumulative Layout Shift)의 주요 원인입니다. Next.js의 next/font폰트 로딩 중 레이아웃 이동을 완전히 제거합니다.

    tsx
    // app/layout.tsx
    import { Inter, Noto_Sans_KR } from 'next/font/google';
    
    const inter = Inter({
      subsets: ['latin'],
      display: 'swap',
      variable: '--font-inter',
    });
    
    const notoSansKr = Noto_Sans_KR({
      subsets: ['korean'],
      weight: ['400', '700'],
      display: 'swap',
      variable: '--font-noto-sans-kr',
    });
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="ko" className={`${inter.variable} ${notoSansKr.variable}`}>
          <body>{children}</body>
        </html>
      );
    }
    css
    /* globals.css */
    body {
      font-family: var(--font-noto-sans-kr), var(--font-inter), sans-serif;
    }

    자동 최적화:

    • 자동 서브셋팅: 사용된 문자만 포함 (한글 11,172자 → 실사용 2,500자)
    • Self-hosting: Google Fonts를 로컬에 호스팅 (DNS 조회 제거)
    • preload: 폰트 파일 사전 로드로 FOIT(Flash of Invisible Text) 방지
    • Size adjustment: 폰트 메트릭 자동 계산으로 CLS 0

    4. 코드 스플리팅과 Dynamic Import

    JavaScript 번들 크기는 FID(First Input Delay)에 직접 영향을 미칩니다. 필요한 코드만 필요한 시점에 로드해야 합니다.

    자동 코드 스플리팅

    Next.js는 app/ 디렉토리의 각 페이지를 자동으로 분리합니다.

    code
    app/
    ├── page.tsx           → home.js (50KB)
    ├── about/page.tsx     → about.js (30KB)
    └── products/page.tsx  → products.js (80KB)

    홈페이지 방문 시 home.js만 로드되며, products.js는 해당 페이지 방문 시 로드됩니다.

    Dynamic Import로 수동 최적화

    Below-the-fold 컴포넌트, 모달, 차트 같은 즉시 필요하지 않은 컴포넌트는 동적 임포트로 분리하세요.

    tsx
    // ❌ 나쁜 예: 모든 컴포넌트 즉시 로드
    import HeavyChart from '@/components/HeavyChart';
    import Modal from '@/components/Modal';
    
    // ✅ 좋은 예: 필요 시점에 로드
    import dynamic from 'next/dynamic';
    
    const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
      loading: () => <ChartSkeleton />,
      ssr: false, // 차트는 클라이언트에서만 렌더링
    });
    
    const Modal = dynamic(() => import('@/components/Modal'));

    성능 향상:

    • 초기 JavaScript 번들 40% 감소
    • FID 평균 60ms → 35ms 개선
    • TTI (Time to Interactive) 1.5초 단축
    💡 동적 임포트 대상
  • 모달, 드로어, 팝업 (사용자 액션 후 표시)
  • 차트, 지도 (무거운 라이브러리)
  • Below-the-fold 섹션 (스크롤 후 보이는 영역)
  • A/B 테스트 변형 컴포넌트

  • 5. 서버 컴포넌트로 클라이언트 JS 줄이기

    Next.js 16 App Router의 서버 컴포넌트는 기본값입니다. 상호작용이 필요 없는 컴포넌트는 서버에서 렌더링해 클라이언트로 전송되는 JavaScript를 줄이세요.

    tsx
    // ✅ 서버 컴포넌트 (기본값, 'use client' 없음)
    async function ProductList() {
      const products = await fetchProducts(); // 서버에서 데이터 페칭
    
      return (
        <div className="grid grid-cols-3 gap-4">
          {products.map((product) => (
            <ProductCard key={product.id} product={product} /> {/* 서버 컴포넌트 */}
          ))}
        </div>
      );
    }
    
    // ✅ 클라이언트 컴포넌트 (상호작용 필요)
    'use client';
    function AddToCartButton({ productId }: { productId: string }) {
      const [loading, setLoading] = useState(false);
    
      return (
        <button onClick={() => addToCart(productId)}>
          Add to Cart
        </button>
      );
    }

    서버 vs 클라이언트 컴포넌트 선택 기준:

    조건컴포넌트 타입
    데이터 페칭, DB 쿼리서버
    useState, useEffect 사용클라이언트
    이벤트 리스너 (onClick, onChange)클라이언트
    브라우저 API (localStorage, window)클라이언트
    정적 콘텐츠 표시서버

    6. 성능 측정과 모니터링

    최적화의 효과를 측정하지 않으면 개선 여부를 알 수 없습니다.

    Vercel Speed Insights (권장)

    Next.js 배포 시 Vercel Speed Insights를 활성화하면 실제 사용자 성능 데이터를 수집합니다.

    tsx
    // app/layout.tsx
    import { SpeedInsights } from '@vercel/speed-insights/next';
    
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html>
          <body>
            {children}
            <SpeedInsights />
          </body>
        </html>
      );
    }

    Lighthouse CI

    CI/CD 파이프라인에 Lighthouse를 통합해 성능 회귀 방지:

    json
    // .github/workflows/lighthouse.yml
    name: Lighthouse CI
    on: [push]
    jobs:
      lighthouse:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - run: npm ci && npm run build
          - uses: treosh/lighthouse-ci-action@v9
            with:
              urls: 'http://localhost:3000'
              budgetPath: './budget.json'
    json
    // budget.json (성능 예산)
    [
      {
        "path": "/*",
        "resourceSizes": [
          { "resourceType": "script", "budget": 300 }, // JavaScript 300KB 이하
          { "resourceType": "image", "budget": 500 }   // 이미지 500KB 이하
        ],
        "timings": [
          { "metric": "interactive", "budget": 3000 }  // TTI 3초 이하
        ]
      }
    ]

    실전 성능 개선 사례

    Before (일반 React SPA):

    • LCP: 4.2초
    • FID: 180ms
    • CLS: 0.28
    • JavaScript 번들: 850KB
    • Lighthouse 점수: 62/100

    개선 작업:

    1. Next.js App Router 마이그레이션
    2. 주요 페이지 SSG 전환
    3. next/image로 전환 (WebP, lazy loading)
    4. next/font로 폰트 최적화
    5. 무거운 라이브러리 동적 임포트
    6. 서버 컴포넌트 최대 활용

    After (Next.js 16 최적화):

    • LCP: 1.1초 (74% 개선)
    • FID: 42ms (77% 개선)
    • CLS: 0.03 (89% 개선)
    • JavaScript 번들: 280KB (67% 감소)
    • Lighthouse 점수: 98/100
    Core Web Vitals Targets LCP Largest Contentful Paint ≤ 2.5s Loading Speed INP Interaction to Next Paint ≤ 200ms Responsiveness CLS Cumulative Layout Shift ≤ 0.1 Visual Stability
    Google uses these metrics as ranking signals

    결론: 성능은 선택이 아닌 필수

    성능은 기능이 아닌 사용자 경험의 핵심입니다. 느린 웹사이트는 아무리 좋은 콘텐츠와 디자인을 가져도 사용자를 잃습니다.

    Next.js는 성능 최적화를 위한 모든 도구를 제공합니다. 올바르게 활용하면 세계 최고 수준의 웹사이트를 만들 수 있습니다.

    POEMA는 Next.js 성능 최적화 전문가로서 평균 Lighthouse 점수 95+ 달성을 보장합니다.

    관련 글