이미지 로딩 중...

Next.js 실전 운영 CDN과 정적 자산 최적화 - 슬라이드 1/9
A

AI Generated

2025. 11. 8. · 4 Views

Next.js 실전 운영 CDN과 정적 자산 최적화

Next.js 애플리케이션의 정적 자산을 CDN을 활용해 최적화하는 방법을 알아봅니다. 이미지, 폰트, CSS 등 다양한 자산을 효율적으로 관리하고 배포하는 실전 기법을 다룹니다. CloudFront, Vercel, 그리고 커스텀 CDN 설정까지 실무에 바로 적용 가능한 내용을 제공합니다.


목차

  1. Next.js 정적 자산 구조 이해
  2. next.config.js CDN 설정
  3. Image 컴포넌트 CDN 최적화
  4. CloudFront 배포 설정
  5. 폰트 파일 최적화
  6. Cache-Control 헤더 전략
  7. 멀티 CDN 전략
  8. 빌드 시점 자산 최적화

1. Next.js 정적 자산 구조 이해

시작하며

여러분이 Next.js로 개발한 서비스가 점점 성장하면서 이미지 로딩이 느려지고, 사용자들이 페이지를 떠나는 경험을 해보셨나요? 특히 해외 사용자들이 "왜 이렇게 느려요?"라는 피드백을 주는 상황, 많은 개발자들이 겪는 문제입니다.

이런 문제의 근본 원인은 모든 정적 자산이 여러분의 서버 한 곳에서만 제공되기 때문입니다. 서울에 있는 서버에서 미국 사용자에게 5MB 이미지를 전송하면, 물리적 거리만큼 시간이 걸립니다.

이는 사용자 이탈률 증가로 직결됩니다. 바로 이럴 때 필요한 것이 Next.js의 정적 자산 구조를 제대로 이해하고 CDN을 활용하는 것입니다.

Next.js는 public 폴더를 중심으로 정적 자산을 관리하며, 이를 CDN과 연결하면 전 세계 어디서든 빠른 속도를 보장할 수 있습니다.

개요

간단히 말해서, Next.js의 정적 자산은 public 폴더에 저장되며, 빌드 시 자동으로 최적화되어 배포되는 구조입니다. 왜 이 구조를 이해해야 할까요?

Next.js는 개발 모드와 프로덕션 모드에서 정적 자산을 다르게 처리합니다. 개발 모드에서는 즉시 반영되지만, 프로덕션에서는 빌드 시점에 최적화와 해싱이 적용됩니다.

예를 들어, logo.png 파일이 빌드 후 logo.a1b2c3d4.png로 변환되어 브라우저 캐시 문제를 자동으로 해결해줍니다. 기존에는 단순히 img 태그로 이미지를 불러왔다면, 이제는 Next.js의 Image 컴포넌트와 public 폴더 구조를 활용하여 자동 최적화를 받을 수 있습니다.

Next.js 정적 자산의 핵심 특징은 세 가지입니다: (1) public 폴더의 파일은 루트 경로로 접근 가능, (2) 빌드 시 자동 해싱과 압축, (3) Image 컴포넌트를 통한 자동 최적화입니다. 이러한 특징들이 중요한 이유는 별도의 복잡한 설정 없이도 기본적인 성능 최적화를 얻을 수 있기 때문입니다.

코드 예제

// public/images/logo.png를 사용하는 올바른 방법
import Image from 'next/image'

export default function Header() {
  return (
    <header>
      {/* public 폴더는 / 로 시작 */}
      <Image
        src="/images/logo.png"
        alt="Company Logo"
        width={200}
        height={50}
        priority // 중요한 이미지는 우선 로딩
      />

      {/* CSS 파일도 public에서 관리 가능 */}
      <link rel="stylesheet" href="/styles/global.css" />
    </header>
  )
}

설명

이것이 하는 일: Next.js는 public 폴더의 모든 파일을 정적 자산으로 인식하고, 빌드 시 이를 최적화하여 사용자에게 가장 효율적인 방식으로 제공합니다. 첫 번째로, Image 컴포넌트를 사용하면 Next.js가 자동으로 이미지를 최적화합니다.

src="/images/logo.png"처럼 슬래시로 시작하는 경로는 public 폴더를 가리킵니다. 왜 이렇게 하는지?

public 폴더 밖의 파일들은 번들링되지만, public 안의 파일들은 그대로 제공되면서도 최적화의 대상이 되기 때문입니다. 그 다음으로, width와 height를 명시하면 Next.js가 Cumulative Layout Shift(CLS)를 방지합니다.

이미지가 로드되기 전에 공간을 미리 확보하여, 페이지가 갑자기 밀리는 현상을 막습니다. 내부적으로는 이 정보를 바탕으로 responsive 이미지 세트를 생성하고, 사용자의 디바이스에 맞는 크기를 자동으로 선택합니다.

priority 속성은 이미지 로딩 우선순위를 높입니다. 마지막으로, 이 설정들이 결합되면 Next.js가 WebP 같은 최신 포맷으로 자동 변환하고, lazy loading을 적용하여 최종적으로 70-80% 빠른 이미지 로딩을 만들어냅니다.

여러분이 이 구조를 사용하면 (1) 자동 이미지 최적화로 대역폭 절감, (2) SEO 개선을 위한 올바른 메타데이터 생성, (3) 브라우저 캐싱 자동 관리라는 이점을 얻을 수 있습니다.

실전 팁

💡 public 폴더에는 robots.txt, sitemap.xml, favicon.ico 같은 루트 레벨 파일들도 저장하세요. 이들은 SEO에 필수적이며 반드시 루트 경로에 있어야 합니다.

💡 이미지 파일명에 버전을 포함하지 마세요 (logo-v1.png 대신 logo.png). Next.js가 빌드 시 자동으로 해시를 추가해 캐시 무효화를 처리합니다.

💡 대용량 이미지는 public 폴더에 직접 넣지 말고 CDN에 업로드하세요. 10MB 이상의 파일은 빌드 시간을 크게 늘리고 배포 패키지를 비대하게 만듭니다.

💡 개발 중에는 public 폴더 변경 사항이 즉시 반영되지만, 프로덕션 빌드 후에는 반드시 재빌드가 필요합니다. 자주 변경되는 자산은 외부 스토리지를 고려하세요.

💡 Image 컴포넌트 대신 일반 img 태그를 쓰면 최적화가 적용되지 않습니다. 반드시 next/image를 사용하여 자동 최적화 혜택을 받으세요.


2. next.config.js CDN 설정

시작하며

여러분의 Next.js 앱이 성공적으로 런칭되고 트래픽이 급증하면서, 서버 비용이 매달 두 배씩 늘어나는 상황을 경험해보셨나요? 특히 이미지와 정적 자산 때문에 서버 대역폭 비용이 폭발적으로 증가하는 것은 스타트업에게 치명적입니다.

이 문제의 해결책은 의외로 간단합니다. 정적 자산을 CDN으로 분리하면 서버 부하는 90% 감소하고, 전 세계 사용자에게 동일한 빠른 속도를 제공할 수 있습니다.

하지만 많은 개발자들이 "CDN 설정이 복잡할 것 같아서"라며 미루다가 큰 비용을 지불합니다. 바로 이럴 때 필요한 것이 next.config.js의 assetPrefix 설정입니다.

단 몇 줄의 코드로 모든 정적 자산을 CDN으로 자동 리디렉션할 수 있으며, 기존 코드는 전혀 수정할 필요가 없습니다.

개요

간단히 말해서, assetPrefix는 Next.js의 모든 정적 자산에 자동으로 CDN 주소를 붙여주는 설정입니다. 왜 이 설정이 필요할까요?

Next.js는 기본적으로 /_next/static/ 경로로 빌드된 자산을 제공합니다. 하지만 이를 여러분의 서버가 아닌 CloudFront나 다른 CDN에서 제공하면, 서버는 오직 API와 SSR에만 집중할 수 있습니다.

예를 들어, 이미지가 많은 전자상거래 사이트의 경우 서버 대역폭 비용을 월 500만원에서 50만원으로 줄일 수 있습니다. 기존에는 모든 이미지와 JS 파일 경로를 하드코딩으로 CDN 주소로 변경했다면, 이제는 설정 파일 한 곳만 수정하면 전체 앱의 자산 경로가 자동으로 변경됩니다.

assetPrefix의 핵심 특징은 세 가지입니다: (1) 환경별로 다른 CDN 주소 적용 가능, (2) 빌드된 모든 자산에 자동 적용, (3) 코드 수정 없이 즉시 적용됩니다. 이러한 특징들이 중요한 이유는 개발 환경에서는 로컬로, 프로덕션에서는 CDN으로 자동 전환되어 개발 생산성과 운영 효율성을 동시에 얻을 수 있기 때문입니다.

코드 예제

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 환경 변수로 CDN 주소 관리
  assetPrefix: process.env.NODE_ENV === 'production'
    ? 'https://d123456.cloudfront.net'
    : '',

  images: {
    // Image 컴포넌트도 CDN 사용
    loader: 'custom',
    loaderFile: './my-loader.ts',
  },

  // 정적 내보내기 시 필요
  trailingSlash: true,
}

module.exports = nextConfig

설명

이것이 하는 일: next.config.js의 assetPrefix는 빌드 시 생성되는 모든 정적 자산의 경로 앞에 지정된 CDN 주소를 자동으로 붙여줍니다. 첫 번째로, process.env.NODE_ENV로 환경을 구분합니다.

개발 환경에서는 빈 문자열을 사용해 로컬 서버에서 자산을 제공하고, 프로덕션에서는 CloudFront 주소를 사용합니다. 왜 이렇게 하는지?

개발 중에는 핫 리로딩과 빠른 피드백이 중요하지만, 프로덕션에서는 전 세계 배포와 캐싱이 중요하기 때문입니다. 그 다음으로, assetPrefix가 적용되면 /_next/static/chunks/main.js 같은 경로가 https://d123456.cloudfront.net/_next/static/chunks/main.js로 자동 변환됩니다.

내부적으로는 Next.js가 HTML을 생성할 때 모든 script, link, img 태그의 경로를 수정합니다. 이 과정은 빌드 시점에 일어나므로 런타임 성능에는 영향이 없습니다.

images 설정에서 custom loader를 지정하면 Image 컴포넌트도 CDN을 사용합니다. 마지막으로, 이 모든 설정이 결합되면 사용자가 페이지를 요청할 때 HTML은 여러분의 서버에서, 모든 자산은 가까운 CDN 엣지에서 제공되어 최종적으로 페이지 로딩 속도가 3-5배 빨라집니다.

여러분이 이 설정을 사용하면 (1) 서버 대역폭 비용 90% 절감, (2) 전 세계 동일한 빠른 속도 제공, (3) 서버는 비즈니스 로직에만 집중 가능이라는 이점을 얻을 수 있습니다.

실전 팁

💡 assetPrefix를 변경하면 반드시 전체 재빌드가 필요합니다. 환경 변수로 관리하여 배포 파이프라인에서 자동으로 적용되도록 구성하세요.

💡 CDN 주소는 반드시 HTTPS를 사용하세요. 혼합 콘텐츠(Mixed Content) 경고는 브라우저가 자산 로딩을 차단할 수 있습니다.

💡 개발 중에도 CDN 테스트가 필요하면 .env.local에 NEXT_PUBLIC_CDN_URL을 설정하고 이를 assetPrefix에 사용하세요. 실제 배포 전에 미리 문제를 발견할 수 있습니다.

💡 API 라우트는 assetPrefix의 영향을 받지 않습니다. /api/* 경로는 여전히 여러분의 Next.js 서버에서 처리되므로 걱정하지 마세요.

💡 trailingSlash 옵션을 true로 설정하면 URL 끝에 슬래시가 추가되어 S3 정적 호스팅과 호환성이 좋아집니다. CloudFront를 S3와 함께 쓴다면 필수입니다.


3. Image 컴포넌트 CDN 최적화

시작하며

여러분의 블로그나 커머스 사이트에 고화질 이미지를 올렸는데, 모바일 사용자들이 5MB 원본 이미지를 다운받느라 데이터를 낭비하고 있다는 사실을 알고 계셨나요? 실제로 대부분의 이미지는 원본 크기의 10%만 사용해도 충분합니다.

이런 문제는 단순히 사용자 경험만 해치는 게 아닙니다. 불필요한 대역폭 사용은 여러분의 CDN 비용을 기하급수적으로 증가시키고, 느린 로딩은 직접적으로 전환율 감소로 이어집니다.

아마존의 연구에 따르면 페이지 로딩이 1초 늦어질 때마다 전환율이 7% 감소합니다. 바로 이럴 때 필요한 것이 Next.js의 Image 컴포넌트와 커스텀 loader입니다.

디바이스별로 최적화된 이미지를 자동 생성하고, WebP 같은 최신 포맷으로 변환하며, CDN을 통해 전 세계에 빠르게 배포할 수 있습니다.

개요

간단히 말해서, Next.js Image 컴포넌트의 커스텀 loader는 이미지 URL을 동적으로 생성하여 각 사용자에게 최적화된 이미지를 제공합니다. 왜 이 기능이 필요할까요?

기본 Image 컴포넌트는 Next.js 서버에서 이미지 최적화를 수행하지만, 이는 서버 리소스를 많이 사용합니다. CloudFront나 Cloudinary 같은 전문 이미지 CDN은 전 세계에 분산된 서버에서 실시간으로 이미지를 최적화하고 캐싱합니다.

예를 들어, 한국 사용자에게는 800px 너비의 WebP를, 미국 레티나 디스플레이 사용자에게는 1600px의 AVIF를 자동으로 제공할 수 있습니다. 기존에는 각 디바이스별로 수동으로 이미지를 리사이징하고 여러 포맷을 준비했다면, 이제는 원본 하나만 업로드하면 CDN이 자동으로 수백 가지 변형을 생성하고 제공합니다.

커스텀 loader의 핵심 특징은 세 가지입니다: (1) URL 쿼리 파라미터로 실시간 이미지 변환 요청, (2) srcset 자동 생성으로 반응형 이미지 지원, (3) CDN 엣지에서의 빠른 처리와 캐싱입니다. 이러한 특징들이 중요한 이유는 서버 부하 없이도 무한대에 가까운 이미지 변형을 제공할 수 있기 때문입니다.

코드 예제

// my-loader.ts - 커스텀 이미지 loader
export default function cloudinaryLoader({
  src,
  width,
  quality
}: {
  src: string
  width: number
  quality?: number
}) {
  // Cloudinary 변환 파라미터 구성
  const params = [
    'f_auto', // 자동 포맷 선택 (WebP, AVIF 등)
    'c_limit', // 비율 유지하며 리사이징
    `w_${width}`, // 너비 지정
    `q_${quality || 75}`, // 품질 (기본 75%)
  ]

  const paramsString = params.join(',')
  // https://res.cloudinary.com/demo/image/upload/f_auto,c_limit,w_800,q_75/sample.jpg
  return `https://res.cloudinary.com/your-cloud/${paramsString}${src}`
}

// 사용 예시
import Image from 'next/image'

export default function Product({ imageUrl }: { imageUrl: string }) {
  return (
    <Image
      src={imageUrl}
      alt="Product"
      width={800}
      height={600}
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
    />
  )
}

설명

이것이 하는 일: 커스텀 loader는 Image 컴포넌트가 이미지를 렌더링할 때마다 호출되어, 현재 뷰포트에 맞는 최적화된 이미지 URL을 생성합니다. 첫 번째로, loader 함수는 src, width, quality 세 가지 파라미터를 받습니다.

src는 원본 이미지 경로, width는 현재 디바이스에 필요한 이미지 너비, quality는 압축 품질입니다. 왜 이렇게 하는지?

Next.js가 브라우저의 뷰포트 크기를 감지하여 자동으로 적절한 width 값을 계산하기 때문입니다. 모바일에서는 400px, 태블릿에서는 800px, 데스크톱에서는 1200px을 요청합니다.

그 다음으로, f_auto 파라미터가 특히 중요합니다. 이것은 Cloudinary에게 "브라우저가 지원하는 가장 최신 포맷을 사용하라"고 지시합니다.

내부적으로는 Accept 헤더를 확인하여 Chrome에는 WebP를, Safari에는 JPEG를 제공합니다. 이 한 줄로 30-50%의 파일 크기 감소를 얻을 수 있습니다.

sizes 속성은 브라우저에게 "이 이미지가 화면의 어느 정도를 차지하는지" 알려줍니다. 마지막으로, 이 정보들이 결합되면 Next.js가 자동으로 srcset을 생성하여 <img srcset="...400w, ...800w, ...1200w">처럼 여러 크기의 이미지를 제공하고, 브라우저가 가장 적합한 것을 선택하여 최종적으로 평균 70% 빠른 이미지 로딩을 달성합니다.

여러분이 이 방법을 사용하면 (1) 모바일 사용자의 데이터 사용량 70% 절감, (2) 서버 CPU 사용량 제로 (CDN이 처리), (3) 최신 이미지 포맷 자동 적용으로 성능 극대화라는 이점을 얻을 수 있습니다.

실전 팁

💡 quality 값은 75-80이 최적입니다. 90 이상은 파일 크기만 크게 늘고 육안으로는 차이를 느끼기 어렵습니다. A/B 테스트 결과 75%에서 가장 좋은 성능/품질 균형을 보입니다.

💡 sizes 속성을 정확히 설정하는 것이 중요합니다. 잘못 설정하면 브라우저가 불필요하게 큰 이미지를 다운로드합니다. Chrome DevTools의 Network 탭에서 실제 다운로드 크기를 확인하세요.

💡 priority 속성은 LCP(Largest Contentful Paint) 이미지에만 사용하세요. 모든 이미지에 쓰면 오히려 우선순위가 없어집니다. 보통 히어로 이미지 1-2개만 적용합니다.

💡 Cloudinary 대신 CloudFront를 쓴다면 Lambda@Edge로 이미지 변환을 구현할 수 있습니다. AWS에서 제공하는 예제 코드를 활용하면 비슷한 효과를 얻을 수 있습니다.

💡 개발 환경에서는 loader를 비활성화하고 로컬 이미지를 사용하세요. 개발 중 CDN 의존성은 빌드 속도를 늦추고 오프라인 개발을 불가능하게 만듭니다.


4. CloudFront 배포 설정

시작하며

여러분이 Next.js 앱을 성공적으로 개발했지만, "배포는 어떻게 하지? CDN은 어떻게 연결하지?"라는 고민에 막혀본 적 있으신가요?

특히 AWS를 처음 접하는 개발자들은 S3, CloudFront, Lambda@Edge 같은 용어들에 압도되어 포기하곤 합니다. 실제로 잘못된 CDN 설정은 성능 향상은커녕 오히려 더 느린 서비스를 만들 수 있습니다.

캐싱 설정이 잘못되면 정적 자산이 매번 origin에서 다운로드되거나, 반대로 업데이트가 반영되지 않는 문제가 발생합니다. 이는 사용자 경험을 크게 해칩니다.

바로 이럴 때 필요한 것이 올바른 CloudFront 배포 설정입니다. S3에 빌드된 파일을 업로드하고, CloudFront를 origin으로 연결하며, 적절한 캐싱 정책을 설정하면 전 세계 어디서든 밀리초 단위로 콘텐츠를 제공할 수 있습니다.

개요

간단히 말해서, CloudFront 배포는 S3에 저장된 Next.js 정적 자산을 전 세계 450개 이상의 엣지 로케이션에서 제공하는 AWS의 CDN 서비스입니다. 왜 CloudFront를 사용해야 할까요?

Next.js의 out 폴더에 생성된 정적 파일들은 그 자체로는 단순한 파일일 뿐입니다. 하지만 S3에 업로드하고 CloudFront를 연결하면, 서울의 사용자는 서울 엣지에서, 런던의 사용자는 런던 엣지에서 동일한 파일을 받습니다.

예를 들어, 일본 사용자가 여러분의 서비스에 접속할 때 서울 서버에서 받는 것보다 도쿄 CloudFront 엣지에서 받으면 10배 빠릅니다. 기존에는 단일 서버에서 모든 지역의 사용자를 처리했다면, 이제는 CloudFront가 자동으로 가장 가까운 엣지로 라우팅하여 항상 최적의 속도를 보장합니다.

CloudFront의 핵심 특징은 세 가지입니다: (1) 전 세계 엣지 로케이션에서의 자동 캐싱, (2) DDoS 방어와 AWS Shield 통합, (3) 세밀한 캐싱 정책 제어입니다. 이러한 특징들이 중요한 이유는 단순한 속도 향상을 넘어 보안과 안정성까지 한 번에 해결할 수 있기 때문입니다.

코드 예제

// AWS CLI로 CloudFront 배포 생성
// 먼저 S3 버킷에 빌드 파일 업로드
aws s3 sync out/ s3://my-nextjs-bucket --delete

// CloudFront distribution 설정 (JSON)
{
  "Comment": "Next.js Static Assets CDN",
  "Origins": [{
    "Id": "S3-my-nextjs-bucket",
    "DomainName": "my-nextjs-bucket.s3.amazonaws.com",
    "S3OriginConfig": {
      "OriginAccessIdentity": "origin-access-identity/cloudfront/ABCDEFG"
    }
  }],
  "DefaultCacheBehavior": {
    "TargetOriginId": "S3-my-nextjs-bucket",
    "ViewerProtocolPolicy": "redirect-to-https",
    "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6", // CachingOptimized
    "Compress": true
  },
  "CacheBehaviors": [{
    "PathPattern": "_next/static/*",
    "TargetOriginId": "S3-my-nextjs-bucket",
    "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",
    "DefaultTTL": 31536000 // 1년
  }]
}

설명

이것이 하는 일: CloudFront 배포는 S3를 origin으로 설정하고, 전 세계 엣지 로케이션에 콘텐츠를 캐싱하여 사용자에게 가장 가까운 곳에서 파일을 제공합니다. 첫 번째로, S3 버킷에 Next.js의 빌드 결과물(out 폴더)을 업로드합니다.

aws s3 sync 명령의 --delete 옵션은 이전 빌드의 오래된 파일을 자동으로 삭제합니다. 왜 이렇게 하는지?

매 배포마다 깨끗한 상태를 유지하여 불필요한 파일이 쌓이는 것을 방지하고, S3 스토리지 비용을 최소화하기 때문입니다. 그 다음으로, CloudFront distribution에서 Origins를 S3 버킷으로 지정합니다.

OriginAccessIdentity(OAI)는 특히 중요한데, 이것은 "CloudFront만 이 S3 버킷에 접근할 수 있다"는 의미입니다. 내부적으로는 S3 버킷 정책에 OAI를 추가하여 직접 S3 URL 접근을 차단하고, 반드시 CloudFront를 통해야만 파일에 접근할 수 있게 합니다.

이는 보안과 비용 절감 모두에 효과적입니다. CacheBehaviors에서 _next/static/* 경로는 특별히 1년(31536000초)의 긴 TTL을 설정합니다.

마지막으로, Compress: true 옵션이 활성화되면 CloudFront가 자동으로 gzip과 Brotli 압축을 적용하여, 최종적으로 사용자는 원본 크기의 20-30%만 다운로드하면서도 완벽한 콘텐츠를 받게 됩니다. 여러분이 이 설정을 사용하면 (1) 전 세계 동일한 빠른 속도 (50-200ms), (2) DDoS 공격 자동 방어, (3) S3 직접 접근 차단으로 보안 강화라는 이점을 얻을 수 있습니다.

실전 팁

💡 배포 후 캐시 무효화(invalidation)를 자동화하세요. aws cloudfront create-invalidation --paths "/*"를 CI/CD 파이프라인에 추가하면 새 배포가 즉시 반영됩니다.

💡 CachingOptimized 정책 대신 CachingOptimizedForUncompressedObjects를 사용하면 이미 압축된 파일의 중복 압축을 방지합니다. 특히 이미 gzip된 자산이 많다면 성능이 향상됩니다.

💡 Lambda@Edge를 사용하면 동적 경로도 CloudFront에서 처리할 수 있습니다. /blog/[slug] 같은 동적 라우트도 엣지에서 렌더링하여 SSR 성능을 10배 높일 수 있습니다.

💡 CloudFront 비용은 데이터 전송량에 비례합니다. 월 1TB까지는 매우 저렴하지만, 그 이상은 급격히 증가합니다. CloudWatch로 사용량을 모니터링하여 예상치 못한 비용을 방지하세요.

💡 Origin Shield를 활성화하면 origin(S3) 요청을 90% 줄일 수 있습니다. 트래픽이 많은 서비스라면 S3 요청 비용을 크게 절감할 수 있는 옵션입니다.


5. 폰트 파일 최적화

시작하며

여러분의 웹사이트가 로딩될 때 텍스트가 처음에는 기본 폰트로 보이다가 갑자기 커스텀 폰트로 바뀌는 "깜빡임" 현상을 경험해보셨나요? 이것은 FOIT(Flash of Invisible Text)나 FOUT(Flash of Unstyled Text)라고 불리며, 사용자에게 매우 불쾌한 경험을 줍니다.

폰트 파일은 종종 간과되지만, 실제로는 웹사이트 로딩 시간에 큰 영향을 미칩니다. 하나의 폰트 패밀리가 5-10개의 파일(Regular, Bold, Italic 등)로 구성되고, 각 파일이 100-500KB라면 총 1-3MB를 다운로드해야 합니다.

이는 느린 3G 네트워크에서는 수십 초가 걸립니다. 바로 이럴 때 필요한 것이 Next.js의 next/font 최적화입니다.

폰트를 자동으로 서브셋팅하고, preload하며, CDN에서 제공하여 폰트 로딩 시간을 90% 줄일 수 있습니다.

개요

간단히 말해서, next/font는 Google Fonts나 로컬 폰트를 자동으로 최적화하고, 빌드 시점에 다운로드하여 자체 호스팅으로 전환하는 Next.js의 내장 기능입니다. 왜 이 기능을 사용해야 할까요?

Google Fonts를 직접 링크하면 구글 서버에 요청이 가고, DNS lookup과 TLS 핸드셰이크로 수백 밀리초가 추가됩니다. 하지만 next/font를 사용하면 빌드 시 폰트를 다운로드하여 여러분의 도메인에서 제공하므로 외부 요청이 제로가 됩니다.

예를 들어, Noto Sans KR을 사용하는 한국 서비스라면 폰트 로딩 시간을 2초에서 200ms로 줄일 수 있습니다. 기존에는 @font-face CSS를 직접 작성하고 font-display 속성을 관리했다면, 이제는 next/font가 자동으로 최적의 설정을 적용하고 필요한 글자만 포함하는 서브셋을 생성합니다.

next/font의 핵심 특징은 세 가지입니다: (1) 자동 서브셋팅으로 파일 크기 70% 감소, (2) preload로 폰트 우선 로딩, (3) 레이아웃 시프트 제로를 위한 자동 size-adjust 적용입니다. 이러한 특징들이 중요한 이유는 폰트 로딩이 Core Web Vitals의 CLS(Cumulative Layout Shift)에 직접적인 영향을 미치기 때문입니다.

코드 예제

// app/layout.tsx - Google Fonts 최적화
import { Inter, Noto_Sans_KR } from 'next/font/google'

// 영문 폰트
const inter = Inter({
  subsets: ['latin'], // 필요한 문자만 포함
  display: 'swap', // FOUT 방지
  variable: '--font-inter', // CSS 변수로 사용
})

// 한글 폰트 - 용량이 크므로 주의
const notoSansKR = Noto_Sans_KR({
  weight: ['400', '700'], // 필요한 굵기만
  subsets: ['latin'], // 한글은 자동 포함
  display: 'swap',
  variable: '--font-noto',
})

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-inter), var(--font-noto), sans-serif;
}

설명

이것이 하는 일: next/font는 빌드 시점에 Google Fonts를 다운로드하고, 필요한 글자만 포함하는 서브셋을 생성하여 자체 호스팅으로 전환합니다. 첫 번째로, subsets 옵션으로 필요한 문자 집합을 지정합니다.

Inter 같은 영문 폰트는 'latin'만 지정해도 충분하지만, Noto Sans KR은 한글 11,172자가 모두 포함되어 수 MB에 달합니다. 왜 이렇게 하는지?

Next.js가 실제로 사용되는 텍스트를 분석하여 필요한 글자만 포함하는 최적화를 수행하기 때문입니다. 실제로는 자주 쓰이는 2,500자 정도만 있어도 대부분의 한국어 콘텐츠를 표시할 수 있습니다.

그 다음으로, display: 'swap' 옵션이 FOUT를 방지합니다. 이는 font-display: swap CSS 속성과 동일하며, "폰트가 로드되기 전에는 시스템 폰트를 보여주고, 로드되면 즉시 교체하라"는 의미입니다.

내부적으로는 브라우저가 최대 3초 동안 시스템 폰트를 표시하다가 커스텀 폰트가 준비되면 교체합니다. 이는 텍스트가 보이지 않는 FOIT보다 훨씬 나은 경험입니다.

variable 옵션으로 CSS 변수를 생성하면 전역적으로 폰트를 사용할 수 있습니다. 마지막으로, 빌드 시점에 폰트 파일이 public/_next/static/media/ 폴더에 저장되고, CSS에 @font-face가 자동 생성되며, preload 링크까지 추가되어 최종적으로 폰트 로딩이 페이지 로딩의 critical path에서 제거됩니다.

여러분이 이 방법을 사용하면 (1) 외부 폰트 서버 요청 제로, (2) 폰트 파일 크기 70% 감소, (3) CLS(레이아웃 시프트) 점수 대폭 개선이라는 이점을 얻을 수 있습니다.

실전 팁

💡 한글 폰트는 용량이 크므로 가변 폰트(Variable Font)를 고려하세요. Pretendard Variable은 9개 굵기가 하나의 파일에 포함되어 총 용량이 더 작습니다.

💡 영문 제목에만 특수 폰트를 쓰고, 본문은 시스템 폰트를 사용하는 것도 좋은 전략입니다. font-family: -apple-system, BlinkMacSystemFont는 다운로드 없이 즉시 렌더링됩니다.

💡 로컬 폰트 파일을 사용한다면 next/font/local을 쓰세요. localFont({ src: './my-font.woff2' })로 회사 전용 폰트도 동일한 최적화를 받을 수 있습니다.

💡 preload 옵션은 기본적으로 활성화되어 있지만, 페이지별로 다른 폰트를 쓴다면 수동으로 관리해야 합니다. 불필요한 preload는 오히려 성능을 해칩니다.

💡 폰트 로딩 상태를 추적하려면 document.fonts.ready Promise를 사용하세요. 폰트 로딩이 완료된 후 애니메이션을 시작하는 등 세밀한 제어가 가능합니다.


6. Cache-Control 헤더 전략

시작하며

여러분의 Next.js 앱을 배포했는데 사용자들이 "업데이트를 해도 예전 버전이 보여요"라는 피드백을 준다면? 또는 반대로 "왜 매번 로딩이 느려요?"라고 묻는다면?

이 두 문제는 모두 잘못된 캐싱 전략 때문입니다. 캐싱은 양날의 검입니다.

너무 길게 설정하면 업데이트가 반영되지 않고, 너무 짧게 설정하면 캐싱의 이점을 전혀 얻지 못합니다. 특히 Next.js처럼 파일 해싱을 사용하는 프레임워크에서는 어떤 파일은 영구 캐싱해도 되고, 어떤 파일은 절대 캐싱하면 안 됩니다.

바로 이럴 때 필요한 것이 올바른 Cache-Control 헤더 전략입니다. 정적 자산은 1년간 캐싱하고, HTML은 항상 최신을 제공하며, API 응답은 상황에 따라 조절하는 세밀한 제어가 필요합니다.

개요

간단히 말해서, Cache-Control 헤더는 브라우저와 CDN에게 "이 파일을 얼마나 오래 캐싱해야 하는지"를 지시하는 HTTP 헤더입니다. 왜 이 헤더가 중요할까요?

브라우저는 기본적으로 모든 리소스를 캐싱하려고 하지만, 어떤 규칙으로 캐싱할지 모릅니다. Cache-Control로 명확히 지시하면 불필요한 네트워크 요청을 99% 줄일 수 있습니다.

예를 들어, main.a1b2c3d4.js 같이 해시가 포함된 JS 파일은 내용이 절대 변하지 않으므로 1년간 캐싱해도 안전합니다. 하지만 index.html은 항상 최신 버전을 확인해야 합니다.

기존에는 모든 파일에 동일한 캐싱 정책을 적용했다면, 이제는 파일 타입과 경로별로 다른 전략을 사용하여 최적의 성능과 최신성을 동시에 얻을 수 있습니다. Cache-Control의 핵심 개념은 세 가지입니다: (1) max-age는 브라우저 캐시 시간, (2) s-maxage는 CDN 캐시 시간, (3) stale-while-revalidate는 백그라운드 업데이트를 허용합니다.

이러한 개념들이 중요한 이유는 각각 브라우저, CDN, 사용자 경험에 직접적으로 영향을 미치기 때문입니다.

코드 예제

// next.config.js - 정적 자산 캐싱 전략
/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        // 해시가 포함된 정적 자산 - 영구 캐싱
        source: '/_next/static/:path*',
        headers: [
          {
            key: 'Cache-Control',
            // 1년간 캐싱, 재검증 불필요
            value: 'public, max-age=31536000, immutable',
          },
        ],
      },
      {
        // 이미지 파일 - 1주일 캐싱
        source: '/images/:path*',
        headers: [
          {
            key: 'Cache-Control',
            // 1주일 캐싱, 만료 후 재검증
            value: 'public, max-age=604800, stale-while-revalidate=86400',
          },
        ],
      },
      {
        // HTML 파일 - 항상 재검증
        source: '/:path*.html',
        headers: [
          {
            key: 'Cache-Control',
            // 캐싱하지 않고 항상 서버에 확인
            value: 'public, max-age=0, must-revalidate',
          },
        ],
      },
    ]
  },
}

module.exports = nextConfig

설명

이것이 하는 일: Next.js의 headers() 함수는 특정 경로 패턴에 맞는 파일에 자동으로 HTTP 헤더를 추가하여 브라우저와 CDN의 캐싱 동작을 제어합니다. 첫 번째로, /_next/static/ 경로는 Next.js가 빌드 시 생성하는 JS, CSS 파일입니다.

이들은 파일명에 해시(예: main.a1b2c3d4.js)가 포함되어 있어 내용이 변경되면 파일명도 바뀝니다. 왜 이렇게 하는지?

immutable 지시어를 추가하면 브라우저가 "이 파일은 절대 변하지 않으니까 만료 시간까지 재검증하지 마"라고 이해하여, 조건부 요청(If-None-Match)조차 하지 않습니다. 이는 네트워크 요청을 완전히 제거합니다.

그 다음으로, /images/ 경로의 stale-while-revalidate가 핵심입니다. 이것은 "캐시가 만료되었어도 일단 오래된 버전을 보여주고, 백그라운드에서 새 버전을 가져와"라는 의미입니다.

내부적으로는 사용자가 즉시 콘텐츠를 보고, 다음 방문 시에는 업데이트된 버전을 보게 됩니다. 이는 성능과 최신성의 완벽한 균형입니다.

HTML 파일의 must-revalidate는 특별히 중요합니다. 마지막으로, max-age=0과 결합되면 브라우저가 매번 서버에 "이 파일 바뀌었어?"라고 물어보고, 바뀌지 않았으면 304 Not Modified로 빠르게 응답받아, 최종적으로 항상 최신 HTML을 보면서도 대역폭은 최소화됩니다.

여러분이 이 전략을 사용하면 (1) 정적 자산 네트워크 요청 99% 감소, (2) 업데이트 즉시 반영, (3) CDN 히트율 95% 이상 달성이라는 이점을 얻을 수 있습니다.

실전 팁

💡 no-cache와 no-store는 완전히 다릅니다. no-cache는 "캐싱하되 매번 검증하라"이고, no-store는 "아예 캐싱하지 마라"입니다. 대부분의 경우 no-cache가 적절합니다.

💡 private vs public을 구분하세요. 사용자별로 다른 콘텐츠는 private으로 설정하여 CDN이 캐싱하지 않도록 해야 합니다. 개인정보 유출을 방지할 수 있습니다.

💡 ETag 헤더를 활용하면 파일 내용이 같으면 304 응답으로 빠르게 처리됩니다. Next.js는 자동으로 ETag를 생성하므로 별도 설정이 필요 없습니다.

💡 Vary 헤더로 Accept-Encoding을 지정하면 gzip과 br(Brotli) 버전을 따로 캐싱합니다. CDN이 올바른 압축 버전을 제공하는 데 필수적입니다.

💡 CDN 캐시를 수동으로 무효화하는 방법을 준비하세요. 긴급 버그 수정 시 1년 캐싱된 파일을 즉시 교체해야 할 수 있습니다. CloudFront는 invalidation API를 제공합니다.


7. 멀티 CDN 전략

시작하며

여러분의 서비스가 급성장하여 하루 수백만 명이 접속하는데, 어느 날 갑자기 CDN 장애로 전체 서비스가 다운되는 상황을 상상해보셨나요? 2021년 Fastly 장애 때 Reddit, GitHub, Stack Overflow 등 수많은 대형 서비스가 동시에 먹통이 된 사례가 있습니다.

단일 CDN에 의존하는 것은 단일 장애점(Single Point of Failure)을 만드는 것입니다. 아무리 AWS CloudFront가 안정적이라도 100% 가동 시간을 보장하지는 않습니다.

더구나 특정 지역에서는 CloudFront보다 다른 CDN이 더 빠를 수 있습니다. 예를 들어 중국에서는 Alibaba Cloud CDN이, 유럽에서는 Cloudflare가 더 나은 성능을 보이기도 합니다.

바로 이럴 때 필요한 것이 멀티 CDN 전략입니다. 주 CDN과 백업 CDN을 구성하고, 지역별로 최적의 CDN을 선택하며, 자동 failover를 설정하여 99.99% 가용성을 달성할 수 있습니다.

개요

간단히 말해서, 멀티 CDN 전략은 두 개 이상의 CDN을 동시에 사용하여 하나가 실패해도 다른 CDN으로 자동 전환되도록 하는 고가용성 아키텍처입니다. 왜 이 전략이 필요할까요?

대규모 트래픽을 처리하는 서비스라면 1분의 다운타임도 수천만 원의 손실로 이어질 수 있습니다. 멀티 CDN은 이런 위험을 분산시킵니다.

또한 지역별 성능 최적화도 가능합니다. 예를 들어, 한국과 일본 사용자는 CloudFront로, 유럽 사용자는 Cloudflare로, 중국 사용자는 Alibaba Cloud로 서비스하면 각 지역에서 최고의 성능을 얻을 수 있습니다.

기존에는 하나의 CDN URL만 사용했다면, 이제는 DNS 기반 로드 밸런싱이나 클라이언트 사이드 failover로 여러 CDN을 지능적으로 활용할 수 있습니다. 멀티 CDN의 핵심 구성 요소는 세 가지입니다: (1) DNS 기반 라우팅으로 지역별 최적 CDN 선택, (2) Health check로 장애 CDN 자동 제외, (3) 동기화된 캐시 무효화로 일관성 유지입니다.

이러한 요소들이 중요한 이유는 사용자가 CDN 전환을 전혀 느끼지 못하면서도 항상 최상의 서비스를 받을 수 있게 하기 때문입니다.

코드 예제

// lib/cdn-loader.ts - 멀티 CDN failover loader
const CDN_PROVIDERS = [
  { name: 'cloudfront', url: 'https://d123.cloudfront.net' },
  { name: 'cloudflare', url: 'https://cdn.example.com' },
  { name: 'bunny', url: 'https://example.b-cdn.net' },
]

let primaryCDN = 0 // 현재 사용 중인 CDN 인덱스

export default function multiCDNLoader({
  src,
  width,
  quality
}: {
  src: string
  width: number
  quality?: number
}) {
  const params = `?w=${width}&q=${quality || 75}`
  const cdn = CDN_PROVIDERS[primaryCDN]

  return `${cdn.url}${src}${params}`
}

// 장애 감지 및 자동 failover
export async function healthCheck() {
  for (let i = 0; i < CDN_PROVIDERS.length; i++) {
    try {
      const response = await fetch(`${CDN_PROVIDERS[i].url}/health`, {
        method: 'HEAD',
        signal: AbortSignal.timeout(3000), // 3초 타임아웃
      })

      if (response.ok) {
        primaryCDN = i
        console.log(`Using CDN: ${CDN_PROVIDERS[i].name}`)
        return
      }
    } catch (error) {
      console.warn(`CDN ${CDN_PROVIDERS[i].name} failed, trying next...`)
    }
  }

  console.error('All CDNs failed!')
}

// 앱 시작 시 health check 실행
if (typeof window !== 'undefined') {
  healthCheck()
  setInterval(healthCheck, 60000) // 1분마다 재확인
}

설명

이것이 하는 일: 멀티 CDN loader는 여러 CDN 중 현재 사용 가능한 CDN을 자동으로 선택하고, 장애 발생 시 즉시 다른 CDN으로 전환합니다. 첫 번째로, CDN_PROVIDERS 배열에 사용할 모든 CDN을 우선순위 순서로 나열합니다.

CloudFront가 가장 빠르고 저렴하다면 첫 번째에, Cloudflare를 백업으로 두 번째에, 마지막 safety net으로 Bunny CDN을 세 번째에 배치합니다. 왜 이렇게 하는지?

대부분의 경우 첫 번째 CDN을 사용하다가, 장애 시에만 다음 CDN으로 넘어가도록 하여 일상적인 비용을 최소화하기 때문입니다. 그 다음으로, healthCheck 함수가 주기적으로 각 CDN의 상태를 확인합니다.

HEAD 요청은 본문을 다운로드하지 않으므로 빠르고 대역폭을 거의 사용하지 않습니다. 내부적으로는 3초 타임아웃을 설정하여 느린 CDN도 장애로 간주합니다.

응답 시간이 너무 길면 사용자 경험이 나빠지기 때문입니다. primaryCDN 변수가 전역적으로 관리되어 모든 이미지 로드가 동일한 CDN을 사용합니다.

마지막으로, setInterval로 1분마다 health check를 실행하면 장애가 복구된 주 CDN으로 자동 복귀하고, 최종적으로 사용자는 항상 가장 빠르고 안정적인 CDN을 통해 콘텐츠를 받게 됩니다. 여러분이 이 전략을 사용하면 (1) CDN 장애 시 자동 복구로 다운타임 제로, (2) 지역별 최적 CDN 선택으로 성능 30% 향상, (3) 단일 CDN 의존성 제거로 협상력 강화라는 이점을 얻을 수 있습니다.

실전 팁

💡 DNS 기반 멀티 CDN은 Route53의 Geo-Proximity 라우팅을 활용하세요. 사용자 위치에 따라 자동으로 가장 가까운 CDN을 선택하여 클라이언트 코드 없이도 최적화됩니다.

💡 각 CDN에 동일한 파일을 동기화하는 스크립트를 작성하세요. S3에서 여러 CDN으로 동시 배포하면 일관성 문제를 방지할 수 있습니다. aws s3 sync를 여러 번 실행하면 됩니다.

💡 비용 모니터링을 자동화하세요. 멀티 CDN은 관리가 복잡하고 의도치 않게 여러 CDN에서 동시에 과금될 수 있습니다. CloudWatch와 Datadog으로 실시간 비용 알림을 설정하세요.

💡 캐시 무효화는 모든 CDN에 동시에 실행해야 합니다. 하나만 무효화하면 사용자가 어떤 CDN을 쓰느냐에 따라 다른 버전을 보게 됩니다. 배포 스크립트에 모든 CDN의 invalidation을 포함하세요.

💡 실제 장애 대응 연습을 정기적으로 하세요. 주 CDN을 의도적으로 차단하고 failover가 제대로 작동하는지 확인하는 Chaos Engineering을 분기마다 실행하면 실제 장애 시 당황하지 않습니다.


8. 빌드 시점 자산 최적화

시작하며

여러분이 Next.js 앱을 빌드했는데 결과물이 수백 MB에 달하고, 배포에 10분 이상 걸리는 경험을 해보셨나요? 빌드 크기가 크면 배포 시간이 길어지고, CI/CD 비용이 증가하며, 무엇보다 사용자가 다운로드해야 할 데이터가 늘어납니다.

많은 개발자들이 "빌드 시간은 어쩔 수 없다"고 생각하지만, 실제로는 불필요한 파일이 포함되거나, 최적화가 누락되거나, 압축이 제대로 되지 않는 경우가 대부분입니다. 예를 들어, source map이 프로덕션에 포함되면 빌드 크기가 2-3배 늘어나고, 소스 코드가 노출되는 보안 문제까지 발생합니다.

바로 이럴 때 필요한 것이 빌드 시점 자산 최적화입니다. Tree shaking으로 사용하지 않는 코드 제거, 이미지 자동 압축, Brotli 사전 압축, 그리고 번들 분석으로 빌드를 극한까지 최적화할 수 있습니다.

개요

간단히 말해서, 빌드 시점 최적화는 Next.js가 프로덕션 빌드를 생성할 때 자동으로 수행하는 다양한 최적화 기법들을 설정하고 강화하는 것입니다. 왜 빌드 시점에 최적화해야 할까요?

런타임에 최적화하면 매 요청마다 CPU를 사용하지만, 빌드 시점에 하면 한 번만 처리하고 그 결과를 모든 사용자에게 제공합니다. 예를 들어, 이미지를 런타임에 압축하면 서버가 매번 CPU를 사용하지만, 빌드 시 압축하면 이미 최적화된 파일을 CDN에서 즉시 제공할 수 있습니다.

이는 10배 이상의 효율 차이입니다. 기존에는 빌드 결과물을 그대로 배포했다면, 이제는 분석 도구로 병목을 찾고, 불필요한 부분을 제거하며, 압축률을 극대화하여 최종 사용자에게 전달되는 데이터를 50-70% 줄일 수 있습니다.

빌드 최적화의 핵심 기법은 네 가지입니다: (1) Tree shaking으로 dead code 제거, (2) Code splitting으로 필요한 코드만 로드, (3) 이미지 자동 압축과 포맷 변환, (4) Brotli/gzip 사전 압축입니다. 이러한 기법들이 중요한 이유는 각각 번들 크기, 초기 로딩, 이미지 성능, 전송 속도에 직접적인 영향을 미치기 때문입니다.

코드 예제

// next.config.js - 빌드 최적화 설정
/** @type {import('next').NextConfig} */
const nextConfig = {
  // 프로덕션에서 source map 제거
  productionBrowserSourceMaps: false,

  // 압축 활성화
  compress: true,

  // 불필요한 파일 제외
  pageExtensions: ['tsx', 'ts', 'jsx', 'js'],

  // 이미지 최적화
  images: {
    formats: ['image/avif', 'image/webp'], // 최신 포맷 우선
    minimumCacheTTL: 31536000, // 1년
  },

  // Webpack 설정 커스터마이징
  webpack: (config, { isServer }) => {
    // 번들 분석 (환경 변수로 활성화)
    if (process.env.ANALYZE === 'true') {
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
      config.plugins.push(
        new BundleAnalyzerPlugin({
          analyzerMode: 'static',
          reportFilename: isServer
            ? '../analyze/server.html'
            : './analyze/client.html',
        })
      )
    }

    // moment.js 로케일 제외 (용량 절감)
    config.plugins.push(
      new (require('webpack')).IgnorePlugin({
        resourceRegExp: /^\.\/locale$/,
        contextRegExp: /moment$/,
      })
    )

    return config
  },
}

module.exports = nextConfig

설명

이것이 하는 일: Next.js 빌드 프로세스에서 다양한 최적화를 활성화하여 최종 사용자에게 전달되는 파일 크기와 개수를 최소화합니다. 첫 번째로, productionBrowserSourceMaps: false는 프로덕션에서 source map을 생성하지 않습니다.

Source map은 디버깅에 유용하지만 파일 크기를 2배로 늘리고, 소스 코드가 그대로 노출되는 보안 문제가 있습니다. 왜 이렇게 하는지?

에러 트래킹은 Sentry 같은 도구를 사용하면 서버 측에서 source map을 적용할 수 있어, 클라이언트에 노출할 필요가 없기 때문입니다. 그 다음으로, webpack 설정에서 BundleAnalyzerPlugin은 각 모듈의 크기를 시각화합니다.

이것은 "왜 내 번들이 5MB나 되지?" 같은 질문에 답해줍니다. 내부적으로는 treemap 형태로 표시하여 어떤 라이브러리가 공간을 많이 차지하는지 한눈에 보여줍니다.

예를 들어, moment.js가 200KB를 차지한다면 day.js(2KB)로 교체하여 100배 감소시킬 수 있습니다. IgnorePlugin으로 moment.js의 로케일 파일을 제외하는 것은 특히 효과적입니다.

마지막으로, 이 모든 최적화가 결합되면 빌드 시간은 조금 늘어나지만(5분 → 7분), 최종 번들 크기는 절반으로 줄고(2MB → 1MB), 사용자의 초기 로딩 시간은 3배 빨라져(6초 → 2초), 최종적으로 전환율과 SEO 순위가 모두 개선됩니다. 여러분이 이 최적화를 적용하면 (1) 번들 크기 50% 이상 감소, (2) 빌드 시 문제 조기 발견, (3) 배포 시간과 스토리지 비용 절감이라는 이점을 얻을 수 있습니다.

실전 팁

💡 ANALYZE=true npm run build로 정기적으로 번들을 분석하세요. 새로운 라이브러리를 추가할 때마다 크기를 확인하면 비대화를 미리 방지할 수 있습니다.

💡 next-bundle-analyzer 패키지를 사용하면 webpack 설정 없이도 간편하게 분석할 수 있습니다. npm install @next/bundle-analyzer로 설치하고 next.config.js에서 withBundleAnalyzer로 감싸면 됩니다.

💡 동적 import를 적극 활용하세요. const HeavyComponent = dynamic(() => import('./Heavy'))로 필요할 때만 로드하면 초기 번들 크기를 대폭 줄일 수 있습니다. 특히 관리자 페이지 같은 일부 사용자만 접근하는 기능에 효과적입니다.

💡 이미지는 빌드에 포함하지 말고 별도 스토리지에 저장하세요. public 폴더의 이미지가 많으면 빌드 크기가 기하급수적으로 증가하고, git 저장소도 비대해집니다. S3나 Cloudinary를 사용하세요.

💡 프로덕션 빌드를 로컬에서 테스트하세요. npm run build && npm start로 실제 프로덕션 모드를 실행하면 개발 모드에서는 발견하지 못한 최적화 문제나 에러를 찾을 수 있습니다.


#Next.js#CDN#Static Assets#Performance#CloudFront

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.