블로그TECH NOTE
개발

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

이 글에서 해결할 것

Next.js 웹사이트 성능은 페이지별 렌더링 전략을 고르고, 이미지와 폰트를 최적화하며, 불필요한 클라이언트 JavaScript를 줄인 뒤 실제 Core Web Vitals로 검증해 개선합니다.

POEMORA · 편집팀2025-01-2012
투명한 서버 계층과 가벼운 조각들이 빠른 빛의 경로로 연결된 모습
Next.js 성능 최적화는 렌더링, 전송량, 자산 로딩을 하나의 흐름으로 다룹니다.
#Next.js#React#성능 최적화#Web Vitals

읽기 전에 핵심만

  1. 페이지 특성에 맞춰 정적·동적 렌더링과 캐싱 전략을 선택합니다.
  2. 이미지와 폰트의 전송량과 레이아웃 이동을 함께 줄입니다.
  3. 클라이언트 JavaScript를 최소화하고 실제 사용자 성능 지표로 검증합니다.

웹사이트 로딩 속도는 사용자 경험과 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): 빌드 타임 사전 렌더링

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. 재생성 완료: 새로운 정적 페이지로 교체

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 이미지뷰포트 진입 시 로드

외부 이미지 최적화

외부 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초 단축

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+ 달성을 보장합니다.

프로덕션 환경의 성능 병목을 진단하고 개선하려면 POEMORA 웹 서비스를 살펴보세요.

FAQ

자주 묻는 질문

Next.js를 사용하면 웹사이트가 자동으로 빨라지나요?

기본 코드 분할과 사전 렌더링 같은 최적화가 도움을 주지만, 모든 사이트가 자동으로 빠른 것은 아닙니다. 라우트별 데이터 처리, 클라이언트 JavaScript, 이미지, 폰트, 외부 스크립트를 실제 환경에서 점검해야 합니다.[1]

next/image를 쓰면 무엇이 달라지나요?

기기에 맞는 크기의 이미지를 제공하고 최신 포맷과 지연 로딩을 활용하며 이미지 영역을 미리 확보해 레이아웃 이동을 줄일 수 있습니다. 원격 이미지 보안 설정과 LCP 이미지 우선순위는 별도로 확인해야 합니다.[2]

성능 최적화 결과는 어떻게 검증하나요?

배포 전 실험실 도구만 보지 말고 배포 후 실제 사용자 데이터의 LCP, INP, CLS를 함께 확인하세요. 변경 전 기준을 남기고 라우트와 기기 환경별로 비교해야 병목을 정확히 찾을 수 있습니다.[4]

REFERENCES

확인한 자료

관련 글