지난 4년간 Next.js 앱의 이미지를 최적화해온 저는 솔직히 말해 2026년의 환경은 next/image가 처음 출시됐을 때와는 완전히 다릅니다. Google의 Core Web Vitals 임계값이 강화되었고, 새로운 이미지 포맷이 성숙해졌으며, next/image 컴포넌트 자체도 여러 번 재작성되었습니다. 이미지가 여전히 2023년 방식으로 구성되어 있다면, 성능(및 순위)에서 손실을 입고 있는 것입니다.

이는 Next.js 문서의 단순 재탕이 아닙니다. 이는 LCP 점수가 실제로 중요한 수십 개의 프로덕션 사이트를 배포하면서 배운 내용입니다 -- 이미지 로딩의 200ms 차이가 1페이지와 3페이지의 차이를 의미했던 곳입니다.

목차

2026년 Core Web Vitals를 위한 Next.js 이미지 최적화

2026년 Core Web Vitals: 무엇이 변했는가

Google은 2025년 말에 Core Web Vitals 임계값을 업데이트했으며, 그 변경사항은 사소하지 않았습니다. 현재 상황은 다음과 같습니다:

지표 2023년 "양호" 임계값 2026년 "양호" 임계값 측정 항목
LCP ≤ 2.5초 ≤ 2.0초 최대 콘텐츠풀 페인트
INP ≤ 200ms ≤ 150ms 상호작용 다음 페인트
CLS ≤ 0.1 ≤ 0.1 누적 레이아웃 시프트
TTFB 해당 없음(CWV 아님) 비공식적으로 ≤ 600ms 첫 바이트까지의 시간

LCP 임계값이 2.5초에서 2.0초로 하락한 것은 이미지가 많은 사이트에 가장 큰 영향을 미쳤습니다. 0.5초는 크지 않은 것처럼 보이지만, 페이지의 60% 이상에서 LCP 요소가 이미지라는 점을 고려하면 상당합니다. 보통 영웅 이미지, 상품 사진, 또는 추천 기사 썸네일입니다.

INP가 이미 FID를 대체했지만, 150ms로 강화된 임계값은 부실하게 구성된 이미지 지연 로딩 스크립트를 포함한 무거운 JavaScript 번들이 상호작용 점수를 낮출 수 있음을 의미합니다.

CLS는 동일하게 유지되었으며, 이는 좋은 소식입니다. 하지만 이미지는 명시적 치수가 없을 때 레이아웃 시프트의 가장 일반적인 원인입니다.

Next.js의 경우 이것이 중요한 이유

Next.js 앱은 본래 JavaScript가 많습니다. React를 배포하고 있고, 프레임워크 코드를 배포하고 있으며, 주의하지 않으면 이미지 최적화 로직도 클라이언트 측에서 배포하고 있습니다. 더 엄격한 LCP 예산과 React 앱의 JS 오버헤드의 조합은 정적 HTML 사이트보다 오류 여지가 적다는 의미입니다.

이것이 바로 우리가 Next.js 개발에 중점을 두는 이유입니다 -- 프레임워크는 놀라운 도구를 제공하지만, 올바르게 구성했을 때만 가능합니다.

next/image 실제 작동 원리

next/image<Image />를 사용할 때 발생하는 과정을 알아봅시다. 파이프라인을 이해하면 더 나은 최적화 결정을 내릴 수 있습니다.

요청 흐름

  1. 빌드 시간: Next.js는 /_next/image?url=...&w=...&q=...를 가리키는 <img> 태그로 HTML을 생성합니다
  2. 첫 번째 요청: Next.js 이미지 최적화 API가 요청을 수신하고, 원본 이미지를 가져오고, 크기를 조정하고, 형식을 변환하고, 결과를 캐시합니다
  3. 후속 요청: 캐시된 버전이 직접 제공됩니다

Next.js 15(2026년 초 현재 안정 버전)에서는 Node.js 환경에서 기본적으로 sharp를 사용합니다. Vercel에서는 엣지 기반 이미지 최적화 서비스를 사용합니다. 다른 플랫폼에서는 구성에 따라 sharp 또는 squoosh로 폴백합니다.

// 기본 사용법 -- 하지만 이것 뒤에 많은 일이 일어나고 있습니다
import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="제품 영웅 샷"
      width={1200}
      height={630}
      priority
      quality={80}
    />
  );
}

priority prop은 생각보다 많은 것을 하고 있습니다. HTML에 fetchpriority="high"를 추가하고, 지연 로딩을 비활성화하고, <head>에 프리로드 <link> 태그를 생성합니다. LCP와 관련해서 왜 이것이 중요한지 나중에 다시 언급하겠습니다.

대부분의 사람들이 건드리지 않는 구성

next.config.js (또는 마이그레이션했다면 next.config.ts)에는 모든 것을 제어하는 images 키가 있습니다:

// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 31536000, // 1년(초 단위)
    dangerouslyAllowSVG: false,
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'your-cms.com',
        pathname: '/assets/**',
      },
    ],
  },
};

formats 배열의 순서가 중요합니다. Next.js는 AVIF를 먼저 시도한 다음 WebP로 폴백합니다. AVIF 인코딩은 느리지만 더 작은 파일을 생성합니다 -- 이 트레이드오프를 이해할 가치가 있습니다.

LCP 문제: 영웅 이미지가 점수를 망치는 이유

거의 모든 감사에서 보는 시나리오입니다: 아름다운 영웅 이미지, 스크롤 없이 보이는 영역 위에 있고, 그리기에 3초 이상 걸립니다. 개발자는 next/image를 사용했고 끝났다고 생각했으며 계속 진행했습니다. 하지만 점수는 끔찍합니다.

일반적인 원인들:

1. `priority` Prop 누락

기본적으로 next/image는 모든 것을 지연 로드합니다. 스크롤 아래 이미지에는 좋지만 LCP 요소에는 치명적입니다. priority가 없으면 브라우저는 JavaScript 하이드레이션 후, 교차 관찰자가 작동한 후에 이미지를 발견합니다.

// ❌ 이것은 LCP 이미지를 지연 로드합니다
<Image src="/hero.jpg" alt="Hero" width={1200} height={630} />

// ✅ 이것은 프리로드합니다
<Image src="/hero.jpg" alt="Hero" width={1200} height={630} priority />

2. 낮은 품질 값으로 과도하게 압축

품질을 50으로 설정하면 작은 것 = 더 빠르다고 생각하는 팀을 봤습니다. 하지만 이미지가 흐릿하면 Chrome의 LCP 알고리즘은 여전히 완전히 그려질 때까지 기다려야 합니다. 그리고 고해상도 화면에서는 70 이하의 품질이 종종 보이는 아티팩트를 트리거하여 디자인이 저품질로 보입니다.

제 경험칙: 사진의 경우 품질 75-85, 텍스트나 선명한 가장자리가 있는 이미지의 경우 품질 90 이상.

3. `sizes`를 올바르게 사용하지 않음

sizes 속성은 CSS가 구문 분석되기 전에 브라우저가 요청할 이미지 너비를 알려줍니다. 이것이 없으면 Next.js는 기본값으로 100vw를 사용하므로 모바일 기기는 데스크톱 크기의 이미지를 다운로드합니다.

<Image
  src="/hero.jpg"
  alt="Hero"
  fill
  priority
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
/>

이 하나의 prop 변경만으로 가장 큰 LCP 개선을 얻었습니다 -- 모바일에서는 때때로 400-800ms입니다.

2026년 Core Web Vitals를 위한 Next.js 이미지 최적화 - 아키텍처

포맷 전쟁: 2026년 AVIF vs WebP vs JPEG XL

포맷 환경이 상당히 정착되었습니다. 현재 상황은 다음과 같습니다:

형식 브라우저 지원(2026) 압축 인코딩 속도 최적 사용처
AVIF 전 세계 약 95% 우수(WebP보다 30-50% 작음) 느림 사진, 영웅 이미지
WebP 전 세계 약 98% 양호(JPEG보다 25-35% 작음) 빠름 범용
JPEG XL 약 45%(Chrome은 제거) 우수 중간 웹 사용 권장하지 않음
JPEG 범용 기본선 빠름 폴백만
PNG 범용 사진의 경우 좋지 않음 빠름 투명도, 스크린샷

JPEG XL은 유망한 사양이었지만 Chrome의 2023년 말 지원 제거 결정은 웹 사용을 효과적으로 죽였습니다. Safari가 지원을 추가했고 Firefox는 부분 지원하지만 이에 의존할 수는 없습니다.

제 권장사항: formats: ['image/avif', 'image/webp']를 설정하고 잊으세요. Next.js는 Accept 헤더를 통해 자동으로 콘텐츠 협상을 처리합니다.

AVIF 인코딩 비용

문서에서 충분히 강조하지 않는 것: AVIF 인코딩은 CPU 집약적입니다. Next.js 서버에 대한 첫 번째 요청에서 1200px AVIF 이미지를 인코딩하는 데 적당한 서버에서 2-5초가 걸릴 수 있습니다. 그 첫 번째 방문자가 비용을 지불합니다.

이를 완화하기 위한 전략:

  • 빌드 시 사전 생성 next export 또는 사용자 정의 빌드 스크립트 사용
  • 내장 이미지 최적화가 있는 CDN 사용 (Cloudflare Images, Imgix, Cloudinary)
  • 배포 후 캐시 워밍 모든 중요 이미지 URL을 히트하는 스크립트로
# 간단한 캐시 워밍 스크립트
#!/bin/bash
URLs=("https://yoursite.com/_next/image?url=%2Fhero.jpg&w=1200&q=80"
      "https://yoursite.com/_next/image?url=%2Fhero.jpg&w=750&q=80")

for url in "${URLs[@]}"; do
  curl -s -o /dev/null -H "Accept: image/avif,image/webp" "$url"
  echo "Warmed: $url"
done

반응형 이미지 제대로 구현하기

Next.js의 반응형 이미지는 어렵지 않지만, deviceSizes, imageSizes, sizes prop이 어떻게 함께 작동하는지 이해해야 합니다.

`fill` 레이아웃 패턴

빌드 시 종횡비를 모르는 이미지(CMS 콘텐츠, 사용자 업로드)의 경우 fill prop을 사용합니다:

<div className="relative aspect-[16/9] w-full">
  <Image
    src={post.featuredImage}
    alt={post.title}
    fill
    className="object-cover"
    sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  />
</div>

relative 위치 지정 및 종횡비가 있는 부모 div는 매우 중요합니다. 이것이 없으면 fill 이미지는 높이 0으로 축소되고 마음이 아팠던 CLS 점수를 얻게 됩니다.

``를 사용한 아트 디렉션

때때로 다양한 화면 크기에 대해 다른 자르기가 필요합니다. next/image는 기본적으로 <picture>를 지원하지 않지만, 이를 해결할 수 있습니다:

// 아트 디렉션 해결책
export function ResponsiveHero({ mobileSrc, desktopSrc, alt }) {
  return (
    <>
      <div className="block md:hidden relative aspect-[9/16] w-full">
        <Image src={mobileSrc} alt={alt} fill priority sizes="100vw" />
      </div>
      <div className="hidden md:block relative aspect-[16/9] w-full">
        <Image src={desktopSrc} alt={alt} fill priority sizes="100vw" />
      </div>
    </>
  );
}

네, 이것은 두 이미지의 HTML을 모두 다운로드하지만, 하나만 렌더링되고 숨겨진 것은 지연 로딩 기본값 덕분에 로드되지 않습니다(priority는 뷰포트와 일치하는 것에만 적용됩니다).

CDN 및 엣지 최적화 전략

Next.js를 자체 호스팅하는 경우(많은 팀이 그렇습니다), 이미지에 대한 CDN 전략이 필요합니다.

옵션 1: Vercel에 맡기기

Vercel의 이미지 최적화는 엣지에서 실행됩니다. 대부분의 프로젝트에서 이것이 가장 쉬운 방법입니다. 2026년 기준으로 Vercel의 Pro 요금제에는 최적화가 포함된 5,000개의 소스 이미지가 포함되며, 추가 이미지는 1,000개당 $5입니다. 엔터프라이즈 요금제에는 맞춤형 가격이 있습니다.

옵션 2: 외부 이미지 최적화 서비스

Cloudinary, Imgix, Cloudflare Images는 모두 loader prop 또는 사용자 정의 로더를 통해 Next.js와 작동합니다:

// next.config.js with Cloudinary
module.exports = {
  images: {
    loader: 'custom',
    loaderFile: './lib/cloudinary-loader.js',
  },
};
// lib/cloudinary-loader.js
export default function cloudinaryLoader({ src, width, quality }) {
  const params = [
    `w_${width}`,
    `q_${quality || 'auto'}`,
    'f_auto',
    'c_limit',
  ];
  return `https://res.cloudinary.com/your-cloud/image/upload/${params.join(',')}${src}`;
}
서비스 무료 티어 Pro 가격(2026) 엣지 노드 AVIF 지원
Cloudinary 월 25크레딧 $89/월(25GB) 60개 이상
Imgix 없음 $100/월(100GB) 전 세계
Cloudflare Images 없음 $5/월(100K 변형) 310개 이상
Vercel(내장) 1,000개 이미지(Hobby) Pro에 포함 엣지

헤드리스 CMS 개발 프로젝트의 경우, 일반적으로 Cloudinary 또는 CMS의 내장 이미지 파이프라인(Sanity, Contentful, Hygraph 모두 적절한 이미지 API를 가지고 있습니다)을 사용합니다.

옵션 3: Cloudflare Polish + Next.js

이미 Cloudflare 뒤에 있으면 Polish 기능이 엣지에서 형식 변환을 처리할 수 있습니다. Next.js 이미지 최적화를 비활성화하고 Cloudflare가 일을 처리하도록 합니다:

module.exports = {
  images: {
    unoptimized: true, // Cloudflare가 처리하도록 합니다
  },
};

이 접근 방식을 크게 좋아하지 않습니다. next/image가 제공하는 반응형 크기 조정을 잃기 때문입니다. 하지만 더 간단한 설정에서는 작동합니다.

중요한 것을 측정하기: 도구 및 벤치마크

측정하지 않으면 개선할 수 없습니다. 여기가 제 테스트 스택입니다:

랩 도구

  • Chrome DevTools Lighthouse(2026년 기준 v12): 여전히 출발점입니다. 확장 프로그램이 없는 시크릿 모드에서 실행합니다.
  • WebPageTest: Dulles, VA로 설정하고 4G를 사용하는 Moto G Power에서. 이것은 현실적인 "느린" 사용자를 나타냅니다.
  • Unlighthouse: 전체 사이트를 일괄 스캔합니다. 잊어버린 페이지를 잡는 것에 놀라울 정도입니다.

필드 데이터

  • Chrome UX Report(CrUX): Google이 순위 신호에 사용하는 실제 데이터입니다. PageSpeed Insights 및 BigQuery에서 사용 가능합니다.
  • web-vitals.js: 앱에 추가하여 실제 사용자 메트릭을 수집합니다:
// app/layout.tsx
import { onLCP, onINP, onCLS } from 'web-vitals';

if (typeof window !== 'undefined') {
  onLCP(console.log);
  onINP(console.log);
  onCLS(console.log);
}

프로덕션에서는 console.log 대신 분석 플랫폼으로 보냅니다. 우리는 Vercel Speed Insights와 BigQuery에 쓰는 사용자 정의 엔드포인트를 조합으로 사용합니다.

2026년 벤치마크 목표

이 해에 감사한 사이트를 기반으로, 이미지가 많은 Next.js 사이트의 "양호"한 모습은 다음과 같습니다:

  • 모바일의 LCP(p75): 1.8초 미만(2.0초 임계값 아래 여유를 제공합니다)
  • 스크롤 없이 보이는 영역의 총 이미지 가중치: 200KB 미만
  • 영웅 이미지 로드 시간: 4G에서 800ms 미만
  • 이미지의 CLS: 0

실제로 효과가 있는 고급 기법

BlurHash를 사용한 흐림 플레이스홀더

Next.js는 정적 임포트에 대해 기본적으로 placeholder="blur"를 지원합니다. 동적 이미지(CMS)의 경우 흐림 데이터 URL을 생성해야 합니다:

import { getPlaiceholder } from 'plaiceholder';

export async function getStaticProps() {
  const { base64 } = await getPlaiceholder('/path/to/image.jpg');
  return {
    props: { blurDataURL: base64 },
  };
}

// 컴포넌트에서
<Image
  src={dynamicUrl}
  alt="동적 이미지"
  fill
  placeholder="blur"
  blurDataURL={blurDataURL}
/>

이것은 LCP를 직접 개선하지 않지만, 인지된 성능을 극적으로 개선하고 CLS를 방지합니다.

HTTP/3 및 조기 힌트

CDN이 HTTP/3를 지원하면(Cloudflare, Fastly, Vercel 모두 그렇습니다), 103 Early Hints를 사용하여 HTML 문서가 완전히 생성되기 전에 LCP 이미지를 보내기 시작할 수 있습니다:

// middleware.ts
import { NextResponse } from 'next/server';

export function middleware(request) {
  const response = NextResponse.next();
  
  if (request.nextUrl.pathname === '/') {
    response.headers.set(
      'Link',
      '</hero.avif>; rel=preload; as=image; type="image/avif"'
    );
  }
  
  return response;
}

CSS `content-visibility`를 사용한 스켈레톤 로딩

많은 이미지가 있는 긴 페이지의 경우, content-visibility: auto는 브라우저가 화면 밖의 콘텐츠 렌더링을 완전히 건너뛰도록 알려줍니다:

.image-grid-item {
  content-visibility: auto;
  contain-intrinsic-size: 300px 200px; /* 예상 크기 */
}

이것은 지난 분기에 최적화한 제품 목록 페이지에서 INP를 30-40ms 감소시켰습니다.

모든 감사에서 보는 일반적인 실수

  1. 장식 SVG에 next/image 사용: 그냥 <img> 태그를 사용하거나 SVG를 인라인하세요. 최적화 파이프라인은 이득이 없는 오버헤드를 추가합니다.

  2. "이미지가 흐릿해 보인다"고 해서 전역적으로 unoptimized 설정: 대신 품질 설정을 수정하세요. unoptimized는 모든 것을 우회합니다.

  3. alt 텍스트 설정 잊음: 이것은 접근성 문제일 뿐만 아니라 -- Google의 이미지 검색은 트래픽을 유도하며 인덱싱을 위해 alt 텍스트가 필요합니다.

  4. minimumCacheTTL 설정 안 함: 기본값은 60초입니다. 부하 상태에서 서버가 같은 이미지를 매분 다시 최적화한다는 의미입니다. 최소한 2592000(30일)으로 설정하세요.

  5. 거대한 소스 이미지 사용: 6000x4000px DSLR 사진을 업로드하고 Next.js가 처리하기를 기대합니다. 소스 이미지를 가장 큰 디스플레이 크기의 최대 2배로 사전 처리하세요.

  6. 네트워크 탭 무시: DevTools를 열고, Img로 필터링하고, 크기별로 정렬합니다. 30초 안에 문제를 찾을 수 있습니다.

프로덕션 사이트에서 이러한 문제로 고민하고 있다면, 정확히 우리가 해결하는 문제입니다. 가격을 확인하거나 직접 연락해주세요 -- 우리는 성능 감사를 독립형 활동으로 수행합니다.

FAQ

next/image가 자동으로 이미지를 AVIF로 변환합니까?

네, next.config.jsimages.formats 배열에 'image/avif'가 있으면 됩니다(Next.js 14 이후로는 기본값으로 포함됨). 브라우저가 image/avif를 포함하는 Accept 헤더를 보낼 때 온디맨드로 변환이 발생합니다. 첫 번째 요청은 인코딩 때문에 느리지만, 후속 요청은 캐시에서 제공됩니다.

AVIF가 실제로 동등한 시각적 품질로 WebP와 비교하여 이미지 파일 크기를 얼마나 줄입니까?

우리의 수백 개의 프로덕션 이미지 테스트에서 AVIF는 평균적으로 WebP보다 30-50% 작고 JPEG보다 50-70% 작습니다. 이득은 사진 콘텐츠에서 가장 극적입니다. 스크린샷 또는 텍스트가 있는 이미지의 경우 차이는 15-25%로 좁혀집니다.

여러 이미지에 priority를 사용해야 합니까? 절약스럽게 사용하세요 -- 실제로 스크롤 없이 보이는 영역의 위에 있고 초기 로드에서 보이는 이미지에만 사용하세요. 2-3개 이상의 이미지에 priority를 추가하면 목적을 무효화합니다. 홈페이지 영웅과 아마도 로고의 경우뿐입니다.

next/image와 priority를 사용해도 LCP가 여전히 느린 이유는 무엇입니까?

가장 일반적인 이유는 서버 응답 시간(TTFB)이 예산을 소비하고 있다는 것입니다. Next.js 서버가 응답하는 데 800ms가 걸리면 이미지가 로드되고 페인트하기 위해 1.2초만 남습니다. 다른 원인: 렌더 차단 CSS, 하이드레이션을 지연시키는 큰 JavaScript 번들, 또는 이미지 소스가 느린 원본 서버에 있습니다.

정적 내보내기와 함께 next/image를 사용할 수 있습니까(next export)? 내장 최적화는 사용할 수 없습니다. 정적 내보내기에는 images.unoptimized: true 또는 Cloudinary나 Imgix와 같은 외부 서비스를 가리키는 사용자 정의 로더가 필요합니다. 이것이 우리가 순전히 정적 사이트에 대해 Astro를 권장하는 이유 중 하나입니다 -- 이미지 처리가 실행 서버를 필요로 하지 않습니다.

헤드리스 CMS에서 next/image를 사용하는 이미지를 어떻게 처리합니까?

next.config.jsimages.remotePatterns에 CMS 이미지 도메인을 추가합니다. 대부분의 헤드리스 CMS 플랫폼(Sanity, Contentful, Storyblok, Hygraph)은 자체 이미지 변환 API를 가지고 있습니다. 사용자 정의 로더를 통해 이러한 API를 사용하거나 Next.js가 최적화를 처리하도록 할 수 있습니다. 우리는 헤드리스 CMS 프로젝트의 경우 일반적으로 CMS의 기본 파이프라인을 선호합니다. 서버 로드를 줄이기 때문입니다.

이미지 최적화가 Core Web Vitals 순위 신호에 미치는 영향은 무엇입니까?

Google은 2025년에 Core Web Vitals이 순위 신호로 남아 있음을 확인했지만, 콘텐츠 관련성이 여전히 지배합니다. 그렇기 말고, 상위 결과에 걸쳐 콘텐츠 품질이 유사한 경쟁적 쿼리의 경우, CWV가 타이브레이커가 될 수 있습니다. 주로 최적화되지 않은 이미지로 인한 LCP 문제를 수정한 후 사이트가 3-8 순위로 이동한 것을 보았습니다.

스크롤 아래의 모든 이미지를 지연 로드해야 합니까?

네, Next.js는 기본적으로 이를 수행합니다(priority를 추가하지 않으면). 기본 loading="lazy" 속성이 next/image가 내부적으로 사용하는 것입니다. 더 이상 JavaScript 기반 지연 로딩 라이브러리가 필요하지 않습니다 -- 브라우저 기본 지연 로딩은 2022년 이후로 모든 주요 브라우저에서 안정적이었습니다.