Next.js Image컴포넌트를 활용한 이미지 최적화

namm'm'm·2025년 7월 18일

Project

목록 보기
3/8

이 글은 개념적 내용(이미지 최적화 in Next.js)과

테스트해본 내용(실전 테스트 in Next.js)으로 구분된다.

🍇이미지 최적화 in Next.js

웹 개발에서 이미지 최적화는 사용자 경험을 좌우하는 핵심 요소다.

Next.js에서 제공하는 Image 컴포넌트를 이용하여 최적화해보려 하는데, 단순히 <img> 태그를 <Image> 컴포넌트로 바꾸는 것만으로 끝내서는 안 될 것 같아서 깊게 고민해보고 정리한 내용이다.



1🍑Image 컴포넌트

Image 컴포넌트를 왜 사용하는거지?

  • 이미지 최적화(jpg파일 ⇒ WebP 변환 등)를 브라우저에 맞게 자동으로 변환해준다.
  • lazy loading 기본제공을 통해 보이는 이미지만 load한다.
  • CLS(Cumulative Layout Shift) 방지를 위한 placeholder 제공을 통해, 이미지 로드 전에도 레이아웃이 흔들리지 않도록 해준다.


2🍑Image 컴포넌트 필수 props

예시를 통해 사용법을 확인해보자

import Image from 'next/image';

export default function ImageTest() {
  return (
    <>
      <Image 
        src="/images/test/Monet_WomanWithAParasol.jpg" 
        alt="테스트01" 
        width={600} 
        height={200} 
      />
    </>
  );
}
  • src 이미지 주소 필수
  • alt 이미지 대체 텍스트 필수
  • width height : 픽셀단위 이미지 크기설정 필수


3🍑Image 컴포넌트 추가 props

import Image from 'next/image'
 
const imageLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
 
export default function Page() {
  return (
    <Image
      loader={imageLoader}
      src="me.png"
      fill
      alt="Picture of the author"
      width={500}
      height={500}
      quality={80}
      loading="lazy"
    />
  )
}
  • fill 부모 요소 크기에 맞춰 이미지를 확장하며, 반응형 디자인을 위해 사용한다. ⭐⭐⭐
  • loader 이미지 url을 생성하는 함수로, src, width, quality 매개변수를 받아서 최적화된 url을 생성한다. 외부이미지를 사용할 때 적절해보인다.
  • quality 1과100사이의 값을 통해 랜더링될 때의 이미지 화질을 조절할 수 있다.
  • loading 이미지의 로딩방식을 결정할 수 있다.
    • “lazy” : 기본설정이며, 뷰포트에 진입했을 때 이미지를 로딩하도록 한다.
    • “eager” : 화면 위치 관계없이 즉시 로딩한다.
  • placeholder 이미지 로드되는동안 보여질 임시 이미지
    • empty : 임시이미지 사용하지 않기
    • blur : 이미지의 흐린 버전을 임시이미지로 사용한다.
    • 정적 이미지가 아니라면, blurDataURL props 필수 ⭐⭐⭐⭐⭐
  • 그외 onLoad onError 등 추가적인 props에 대한 정보는 공식문서를 참고해보자.


4🍑이미지 어디에 저장할거야

Next.js에서 이미지를 저장할 위치는 public 혹은 src 중에서 고민해 볼 수 있다.
물론, local image가 아닌 remote image를 활용할 수 도 있다.

src

import testImage from '../assets/test.jpg';

<Image src={testImage} alt="Hero" placeholder="blur" />

public

<Image src="/images/hero.jpg" alt="Hero" width={1920} height={1080} />

차이가 뭔데!

  • src 폴더 내에 있는 이미지이며, import 되어 사용된 이미지
    1. src 폴더 내에 있어야만 import가 가능하다.
    2. src 폴더 내에 있더라도 import 하지 않았다면, 빌드시에 제외되며, 사용할 수 없는 이미지이다.
    3. 서버 빌드시에 webpack에 의해 메타데이터 추출
    4. 그 외 blur 전용 base64생성 + 파일명 해시 등등 추가
    5. 많은 이미지를 src에서 처리한다면, JS번들 크기 증가로인해 초기 로딩 시간 증가함.
    6. 실제 개발과정에서도 관리하기 불편함.
    7. 때문에 정말 핵심적인 이미지만 정적으로 사용하는게 효과적이다.
  • public에서 관리되는 local 이미지
    1. public 폴더 내에 있는 이미지는 빌드시에 webpack이 처리해주지 않고 원본 그대로 유지됨.
    2. public 폴더 내에 있는 이미지는 import가 불가능하다.
    3. public 폴더 내에 있는 이미지는 url로 접근해야한다.
    4. 때문에 사용자 요청시(런타임)에 메타데이터 추출 후 최적화가 진행된다.
    5. 이점을 상쇄시켜줄 무기가 바로 Image 컴포넌트이다. 동적이지만 빠른 최적화를 도와주니깐.
    6. 때문에 보통 핵심이미지 외에는 public에서 관리하고 Image 컴포넌트를 사용한다.


5🍑 sizes and srcset props

sizes와 srcset은 화면에 보여질 이미지 크기를 결정하는게 아니다. 이미지파일의 사이즈(용량/해상도)선택을 위한거다. 화면에 보여질 크기는 width와 heigth가 결정한다.

모바일 사용자와 PC사용자에게 동일한 크기(용량)의 이미지를 랜더링 시킨다면, 데이터 낭비가 발생하겠지? 사용자의 브라우저 크기에 따라 다른 버전의 이미지를 보여주도록 해주는게 srcset 이다.

예를들어 모바일크기의 화면에서는 2MB 이미지 대신 500KB 이미지를 다운로드 시키도록 한다.

예시 코드 : srcset 를 명시한 코드

<img 
  src="image-400.jpg" 
  srcset="
    image-400.jpg 400w,
    image-800.jpg 800w,
    image-1200.jpg 1200w
  "
  alt="이미지"
>

예시코드 2 : Image 컴포넌트는 내부적으로 srcset를 자동 생성해준다.

<Image 
  src="/hero.jpg"
  width={800}
  height={400}
  sizes="(max-width: 768px) 100vw, 50vw"
  alt="Hero Image"
/>
  • sizes는 브라우저에게 이미지가 실제로 표시될 크기를 알려주는 힌트의 역할이다. 즉, 브라우저가 srcset에서 적절한 이미지를 선택할 수 있도록 한다.
  • sizes를 사용하지 않는다면, 브라우저는 100vw로 인식하며, 뷰포트 전체너비를 차지하도록 하며, 의도치 않은 큰 이미지를 다운로드 시키게 된다.
  • fill 속성을 사용하면, 부모요소에 대한 sizes를 계산한다.

sizes 문법

**sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"**
  • 사용자 브라우저 최대 크기가 768px (모바일) 일때는 100vw(화면너비100%)정도의 이미지를 다운로드하도록하여, 1200px쯤되는 큰 이미지를 다운로드 하지 않도록 한다.
  • 1200px 태블릿 정도 일때는 50vw 크기 제한의 이미지 다운로드
  • 그 이상 데스크톱 크기에서는 1/3 너비 정도의 이미지 다운로드
sizes="(max-width: 768px) 100vw, 800px"
  • 모바일에서는 100vw
  • 그 외 최대 800px


6🍑Next.config.js 추가 설정은 공식문서를 참고하자

  • 외부 이미지를 사용한다면? loaderFileremotePatterns 설정
  • SVG 이미지를 사용한다면? dangerouslyAllowSVGcontentSecurityPolicy 설정
  • 이미지 사이즈 일관성을 위해, imageSizes 설정


🍇실전 테스트 in Next.js

  • 주의 : SSR환경에서의 Image컴포넌트의 최적화 성능을 정확하게 측정하기 위해서는 빌드 후 테스트 필수npm run build npm run start
  • 이미지 출처 : wikimedia commons
  • 이미지 크기 : 16mb 3.58mb 700kb 3가지 이미지로 테스트 진행


Test 1 🍑 img태그 vs Image컴포넌트

  • 테스트 환경 : disable cache slow 4g
import Image from 'next/image';

export default function ImageTest() {
  return (
    <>
      <div className="text-4xl p-8 text-center font-paperlogy">Test 01 : img태그와 Image컴포넌트 비교</div>
      <hr />
      <div className="w-full justify-center flex gap-8 ">
        <div>
          <LabelTag text="Image컴포넌트 (700kb)" />
          <Image src="/images/test/Monet_LuncheonOnTheGrass.jpg" alt="t-동적이미지-700kb" width={300} height={200} />
          <LabelTag text="Image컴포넌트 (3mb)" />
          <Image src="/images/test/Monet_ImpressionSunrise.jpg" alt="t-동적이미지-3mb" width={300} height={200} />
        </div>
        <div>
          <LabelTag text="img태그 (700kb)" />
          <img src="/images/test/Monet_LuncheonOnTheGrass.jpg" alt="t-동적이미지-700kb" width={300} height={200} />
          <LabelTag text="img태그 (3mb)" />
          <img src="/images/test/Monet_ImpressionSunrise.jpg" alt="t-동적이미지-3mb" width={300} height={200} />
        </div>
      </div>
    </>
  );
}

const LabelTag = ({ text }: { text: string }) => {
  return <div className="mt-4 mb-2 bg-no2 rounded-lg p-0.5 text-xl font-paperlogy text-center">{text}</div>;
};

예상했던 결과

  1. Time(다운로드시간) : Image컴포넌트이 빠름
  2. Image 컴포넌트는 웹에 최적화된 형식인 jpg ⇒ webp로 변환시킨다.
  3. Image 컴포넌트는 용량 최적화 시킨다. 3mb ⇒ 8kb 700kb => 36kb
  4. img태그의 Path는 public 폴더의 정적파일에 직접접근한다. /images/test/Monet_LuncheonOn...
  5. Image 컴포넌트의 Path는 /_next/image : Next.js의 내장 이미지 최적화 API 엔드포인트
    1. Next.js의 최적화 서버를 거친 뒤에 접근하며 Path가 부여된다.

??? 3mb(Image 컴포넌트) , 700kb(Image 컴포넌트) Time 순서가 할 때마다 다르내?

  1. Image 컴포넌트에서 3mb와 700kb간의 다운로드 시간 차이에는 많은 요인들이 영향을 준다.
    1. 우선, 둘 간의 순서 차이는 일관되지 않았다. (테스트할때마다 차이발생)
    2. 브라우저 캐시 + 서버캐시 (disable cache 했음)
    3. 원본크기에 따른 최적화 작업시간 차이
    4. 최적화된 파일의 네트워크 전송시간차이

결론 : 여러가지 요인들을 예상 해볼 수 있지만 명확한 원인을 찾지는 못했다.

??? 700kb(img태그)가 먼저 화면에 나타나는데?

  1. 1초에 가장먼저 등장하지만 화질이 낮은상태이며, 14초에 다운로드가 완료된다.
  2. 그와중에 3mb(img태그)는 이미지의 상단부터 조금씩 다운로드된다.
  3. 즉, 동일한 img태그를 사용했지만 이미지가 렌더링되는 방식이 다르게 나타난다.
  4. img 태그 이미지의 파일 형식이 jpg => jpeg 로 변환되었다.
  5. jpeg 파일은 로컬환경에서 .jpg 로 보여진다.
  6. jpeg 파일은 인코딩방식에 따라 두가지로 구분된다.
    • Progressive: 전체 이미지를 점진적 품질로 렌더링
    • Baseline: 받은 부분까지만 렌더링

결론 : 즉, 700kb jpg 이미지는 Progressive jpeg 이미지 파일이어서 화질낮은 상태로 먼저 렌더링 된 뒤 화질이 높아지는 방식이다. 3mb 이미지는 Baseline jpeg 이미지 파일이라서 쪼금씩 상단부터 렌더링되는 것이다.



🍇결론

Nextjs 환경에서 이미지를 사용한다면, 꼭 최적화에 대해 공부해보길 바란다. 이번 공부를 통해 정적이미지와 동적이미지의 차이에 대해 이해했으며, Image 컴포넌트의 사용법에 대해서도 익힐 수 있었다. 특히 다양한 device에서 접근 가능한 웹페이지의 특성상 device별로 최적화된 이미지를 제공할 수 있는 방법에 대한 고민이 필요했었다. 그 방법에 대한 힌트를 sizes, srcset으로부터 어느정도 얻을 수 있었던것 같다. 추후에 진행할 최적화작업에서 한번더 깊게 고민해 볼 예정이며, 오늘은 여기까지 할란다.

🍇참고

0개의 댓글