Next.js에서 Image 태그가 동작하는 원리

Intro

Lighthouse로 웹 성능을 검사하다 보면, LCP의 가장 큰 걸림돌은 이미지라는 것을 알게된다.

이는 곧, 웹 성능에서 이미지가 차지하는 비중이 크다는 것을 의미한다.

고맙게도 Next.js의 <image> 태그는 HTML의 <img> 태그를 확장하여 이미지 최적화를 자동으로 제공한다.

이 글에서는 Next.js가 어떤 방식으로 이미지를 최적화해주는지 알아보자.

1. <Image>

Next.js의 <image>는 대표적으로 4가지의 최적화를 제공한다.

  • 크기 최적화: 각 장치에 적합한 크기의 이미지를 자동으로 제공하고, WebP 및 AVIF와 같은 최신 이미지 형식을 사용
  • 시각적 안정성: 이미지가 로드될 때 레이아웃 이동(CLS)을 자동으로 방지
  • 빠른 페이지 로드: 네이티브 브라우저 lazy loading을 사용하여 이미지가 뷰포트에 들어올 때만 로드되며, 선택적 blur-up 플레이스홀더를 제공
  • 자산 유연성: 원격 서버에 저장된 이미지를 포함하여 온디맨드로 이미지 크기를 조정

2. Image Sizing

CLS

Next.js는 시각적 안정성을 위해 렌더링에 따른 레이아웃 이동(Cumulative Layout Shift) 을 방지해준다.

레이아웃 이동이란 이미지가 로드되면서 페이지의 다른 요소들을 밀어내는 것인데, 이는 사용자에게 시각적으로 불편함을 주고 클릭의 오작동으로 사용자에게 예측하지 못한 피해를 줄 수 있기 때문에, 사용자 경험을 저해하는 중요한 지표이다.

따라서 레이아웃 이동을 방지하기 위해 Next.js는 항상 이미지의 사이즈를 전달받는다.

이를 통해 이미지 로드 전 브라우저에서 이미지가 필요한 공간을 예약함으로써 레이아웃 이동을 방지할 수 있다.

이를 위해 로컬이냐 원격이냐에 따라 <image>의 사용 방법이 달라진다.

로컬 이미지

import Image from 'next/image';
import profilePic from './me.png';

export default function Page() {
  return (
    <Image
      src={profilePic}
      alt="Picture of the author"
      // width={500} 자동 제공
      // height={500} 자동 제공
      // blurDataURL="data:..." 자동 제공
      // placeholder="blur" // 로딩 중 선택적 블러 업
    />
  );
}

프로젝트 내부에서 import 형식으로 가져오는 이미지의 경우, Next.js는 파일의 widthheight를 스스로 결정한다.

원격 이미지

import Image from 'next/image';

export default function Page() {
  return (
    <Image
      src="https://s3.amazonaws.com/my-bucket/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
      //   fill={true}
    />
  );
}

원격 이미지를 사용하기 위해서는 src 속성의 값이 문자열이어야한다.

또한, Next.js는 빌드 과정에서 원격 파일에 접근할 수 없기 때문에 width, height 및 선택적 blurDataURL 속성을 수동으로 제공해야 한다.

그리고 공식문서에서 가장 헷갈렸던 내용인데 <Image>widthheight렌더링했을 때의 이미지 사이즈를 결정하지 않는다.

이게 무슨말이냐면 Next.js가 시각적 안정성을 위해 width와 height를 받는 이유는 이미지의 고유한 비율(Aspect Ratio) 을 미리 결정하기 위해서다.

따라서 렌더링했을 때의 이미지 사이즈는 width와 height보다 작은 부모 컴포넌트, 이를테면 이미지를 담는 div, 에 적용된 CSS에 따라 동적으로 변할 수 있으며, 단순히 비율을 고정해주는 역할을 한다.

만약 부모요소를 이용하여 동적으로 이미지의 렌더링 사이즈를 조절하거나, 이미지의 비율을 모르는 경우 fill 속성을 사용해서 암시적으로 부모 요소를 채우도록 이미지를 확장시킬 수 있다.

단, 이미지가 올바르게 렌더링되기 위해 부모 요소에 position: relative, display: block가 적용되어 있어야 한다.

그렇지 않으면 부모 요소의 크기가 0으로 계산되어 이미지가 보이지 않을 수 있다.

3. priority

import Image from 'next/image';
import profilePic from '../public/me.png';

export default function Page() {
  return <Image src={profilePic} alt="Picture of the author" priority />;
}

<Image> 컴포넌트의 priority는 각 페이지의 LCP(Largest Contentful Paint) 요소가 될 이미지에 추가해주는 속성이다.

이를 통해 Next.js는 이미지 로드를 특별히 우선시하여 (예: 프리로드 태그 또는 우선순위 힌트를 통해) LCP를 의미 있게 향상시킬 수 있다.

next dev를 실행할 때 LCP 요소가 priority 속성이 없는 <Image>인 경우 콘솔에 경고가 표시된다.

마무리

Next.js의 <Image> 컴포넌트는 이미지 최적화에 강력한 도구다.

로컬 이미지의 자동 크기 조정, 레이아웃 이동 방지, lazy loading, priority 속성 등 Next.js가 제공하는 강력한 옵션들을 잘 활용하면 웹 성능을 크게 향상시킬 수 있으며, 사용자 경험을 개선할 수 있다.

Reference