[Next.js] 이미지 최적화

프론트엔드에서 최적화 1순위는
이미지 최적화 입니다.

 

웹 개발에 있어서 이미지 최적화는 사용자 경험과 성능 향상에 필수적입니다. 특히 모던 웹에서는 이미지 로딩 속도가 페이지 로딩 시간에 큰 영향을 미치므로, 이를 효율적으로 관리하는 것이 중요합니다. 

 

다행히도 Next.js 에서는 자체적으로 Image 라는 컴포넌트를 제공함으로써

코드 몇줄만으로 이미지를 최적화 시켜줄 수 있습니다.

 

다음 그림과 같이 일반적인 <img/> Tag<Image/> Tag 를 비교해 봐도 확연하게 차이가 나는 것을 보실 수 있습니다.

 

기본적인 형식

Image 컴포넌트는 아래와 같은 properties 가 필수적으로 들어가야 합니다.

import Image from 'next/image'
 
export default function Page() {
  return (
    <Image
      src="/profile.png"
      width={500}
      height={500}
      alt="Picture of the author"
    />
  )
}

 

src

local image 파일, 외부 이미지 파일이 들어갑니다. 

일반적으로 local image 파일을 넣어주는 경우 public 폴더 안에 이미지를 저장하면 /profile.png 처럼 경로 설정을 하지 않고도

불러올 수 있습니다. 하지만 외부 URL을 사용할 경우, 해당 URL을 next.config.js의 remotePatterns에 추가해야 합니다.

remotePatterns 를 설정하는 방법은 아래에 나와 있습니다.

 

width
width 속성은 이미지의 넓이를 정해줍니다.

이 속성은 이미지를 로드하는 동안 레이아웃 변동을 방지하고 올바른 가로 비율을 설정하는데 유용합니다.

나중에 아래에서 배우게 될 blur 기능에서 미리 보여줄 이미지의 넓이를 정해줍니다.

blur 을 간단하게 설명하면 이미지가 완전히 로드되기 전에 저해상도 버전을 먼저 보여주는 기능입니다.
정적으로 import된 이미지나 fill 속성이 있는 이미지를 제외하고는  width, height 속성은 필수입니다.

 

height
height 속성은 이미지의 높이를 정해줍니다.

이 속성은 이미지를 로드하는 동안 레이아웃 변동을 방지하고 올바른 세로 비율을 설정하는데 유용합니다.
나중에 아래에서 배우게 될 blur 기능에서 미리 보여줄 이미지의 높이를 정해줍니다.

 

alt
alt 속성은 스크린 리더와 검색 엔진을 위해 이미지를 설명하는 데 사용됩니다.

또한 이미지를 비활성화했거나 로딩 중 오류가 발생한 경우 표시되는 대체 텍스트입니다.

결론적으로 width와 height 속성을 결합하면, 브라우저가 이미지를 로드하기 전에
이미지를 위한 공간을 예약하는 데 사용되는 가로 세로 비율이 결정됩니다. 

 

options

<Image /> 컴포넌트는 필수 속성 외에도 여러 가지 추가 속성을 허용합니다.

여기서는 가장 자주 사용되는 속성들을 설명하고 있으며, 더 드물게 사용되는 속성에 대한 자세한 내용은

고급 속성 섹션에서 찾을 수 있습니다.

loader

이미지 URL을 해석하는 데 사용되는 커스텀 함수입니다.

loader는 다음과 같은 매개변수를 받아 이미지 URL 문자열을 반환하는 함수입니다.

외부에서 들어오는 이미지 파일을 커스텀 해야 할 때 사용되거나 이미지 주소에 어떤 일을 하고 싶을 때 사용합니다.

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"
      alt="저자의 사진"
      width={500}
      height={500}
    />
  )
}

 

또는 next.config.js 파일에서 loaderFile 설정을 사용하여

애플리케이션 내의 모든 <Image /> 컴포넌트에 대한 로더를 설정할 수 있습니다.

// imageLoader.js
export const myLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`;
};


// next.config.js
module.exports = {
  images: {
    loaderFile: './path/to/imageLoader.js', // 커스텀 로더 파일 경로
  },
};

 

fill

이미지가 부모 요소를 채우도록 하는 불리언 값입니다. 이는 너비와 높이를 알 수 없을 때 유용합니다.

부모 요소는 position: "relative", position: "fixed" 또는 position: "absolute" 스타일을 지정해야 합니다.

기본적으로 img 요소에는 자동으로 position: "absolute" 스타일이 할당됩니다.

 

이미지에 스타일이 적용되지 않은 경우, 이미지는 컨테이너에 맞게 확장됩니다.

이미지의 가로 세로 비율을 유지하면서 컨테이너에 맞추려면 object-fit: "contain" 속성을 설정할 수 있습니다.

반대로 object-fit: "cover" 속성을 사용하면 이미지가 컨테이너 전체를 채우며, 비율을 유지하면서 잘리게 됩니다.

fill 값을 이용하는 경우 width, height 를 설정하실 필요가 없습니다.

sizes

미디어 쿼리와 유사한 문자열로, 이미지가 다양한 화면 크기에서 얼마나 넓게 표시될지를 나타냅니다.

sizes 값은 fill 속성을 사용하는 이미지나 반응형 크기를 가진 이미지의 성능에 큰 영향을 미칩니다.

 

예를 들어, 스타일에 따라 이미지가 모바일에서는 전체 너비로,

태블릿에서는 2열 레이아웃으로, 데스크탑에서는 3열 레이아웃으로 표시될 것을 알고 있다면, 다음과 같은 sizes 속성을 포함해야 합니다.

import Image from 'next/image'

export default function Page() {
  return (
    <div className="grid-element">
      <Image
        fill
        src="/example.png"
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
      />
    </div>
  )
}
하지만 sizes 속성을 직접 설정하지 않으면 기본값으로 100vw가 사용되며, 이는 화면 너비 전체 를 차지하는 이미지로 간주됩니다.

 

quality

이미지의 최적화 품질을 나타내며, 1부터 100 사이의 정수 값입니다.

100은 가장 높은 품질이지만 파일 크기도 가장 큽니다. 기본값은 75입니다.

import Image from 'next/image'
 
export default function Page() {
  return (
    <Image
      src="/profile.png"
      width={500}
      height={500}
      alt="Picture of the author"
      quality={75}
    />
  )
}

 

priority

priority={false} // {false} | {true}

 

true일 경우, 해당 이미지는 높은 우선순위를 가지며 미리 로드됩니다.

priority 속성이 적용된 이미지는 지연 로딩(lazy loading)이 자동으로 비활성화됩니다.

priority 속성은 페이지 상단에서 바로 보이는 이미지에만 사용해야 하며, 기본값은 false입니다.

 

이미지 컴포넌트 설정 옵션

Next.js에서 <Image /> 컴포넌트는 next.config.js 파일에서 다양한 설정을 지원합니다. 

 

Next.config.js 에서 deviceSizes 와 imageSizes 를 설정

deviceSizes 와 imageSizes 설정은 next/image 에서 자동으로 생성되는 srcset 과 sizes 값을 최적화 하는 역할을 합니다.

 

deviceSizes 는 반응형 이미지를 지원하기 위해 다양한 화면 크기에 맞는 이미지 크기를 지정합니다.

예를 들어, 사용자가 640px 너비의 기기를 사용할 때는 이 설정에 맞는 이미지를 자동으로 선택하여 다운로드 하게 됩니다.

 

imageSizes 는 고정된 크기의 이미지에 대해 사용할 수 있는 크기 목록입니다.

이 설정을 적용하면 브라우저에서 자동으로 이미지 크기를 최적화하여, 해당 기기에 맞는 적절한 이미지 파일을 선택해 다운로드하게 됩니다. 즉, 각 기기의 뷰포트에 맞춰 가장 적합한 이미지를 불러오기 때문에 성능이 최적화 됩니다.

 

예를 들어 image-container 가 730px 라고 가정하면 sizes 를 100vw 로 설정했을 대 이미지 크기는 730px 가 됩니다.

하지만 deviceSizes 에서 설정한 값을 보면 640 ~ 750 px 설정이 있어서 750px 의 이미지를 최종적으로 로드해 줍니다.

 

원본 이미지가 1800px 이라 하더라도, 조건에 따라 적절한 크기의 이미지를 로드하게 되므로, 불필요하게 큰 이미지를 다운로드 하지 않게 됩니다. 이렇게 하면 데이터 전송량이 줄어들고, 페이지 로딩 속도가 빨라져 성능이 향상됩니다.

// next.config.js
module.exports = {
  images: {
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  },
};
import Image from 'next/image';

export default function MyComponent() {
  return (
    <div className="image-container">
      <Image
        src="/example.png"
        alt="Example image"
        fill 
        sizes="(max-width: 732px) 90vw, (max-width: 992px) 45vw, 320px"
      />
    </div>
  );
}

 

remotePatterns

Next.js 이미지 최적화 API에서 외부 이미지를 사용하려면 보안 목적으로 설정이 필요합니다.

이러한 보안 목적은 외부에서 들어오는 공격에 대한 방어책으로써 Next.js 가 제공하는 방법이므로 꼭 설정해 주어야 합니다.

이를 통해 허용된 외부 이미지만 사용할 수 있습니다. 아래는 remotePatterns를 사용하는 예시입니다.

// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        port: '',
        pathname: '/account123/**',
      },
    ],
  },
}

 

위 예시는 next/image 컴포넌트의 src 속성이 https://example.com/account123/로 시작하는지 확인합니다.

이 외의 프로토콜, 호스트명, 포트, 경로가 일치하지 않으면 400 Bad Request 응답을 반환합니다.

 

다음은 다른 예시입니다

// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.example.com',
        port: '',
      },
    ],
  },
}

 

위 예시는 https://img1.example.com 또는 https://me.avatar.example.com와 같은 하위 도메인이 있는 모든 example.com 도메인을 허용합니다. 다른 프로토콜이나 포트가 있으면 400 Bad Request가 반환됩니다.

 

주의할 점은 중간에 **를 사용할 수 없습니다.
프로토콜, 포트, 경로 이름을 생략하면 ** 가 암묵적으로 포함되지만 보안 문제로 인해 권장되지 않습니다.

 

formats

Next.js는 기본적으로 브라우저가 지원하는 이미지 형식을 자동으로 감지하여 해당 형식을 제공합니다.

기본값은 image/webp입니다. 아래처럼 굳이 안해주셔도 됩니다.

// next.config.js
module.exports = {
  images: {
    formats: ['image/webp'],
  },
}

 

AVIF 형식 지원을 추가하려면 다음과 같이 설정합니다.

// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
}

 

AVIF는 WebP보다 20% 더 오래 인코딩되지만 20% 더 작게 압축됩니다

 

하지만 그림에서 보시는 바와 같이 avif 를 아직 지원하지 않는 브라우저가 존재하고 있습니다.

그러면 avif 를 사용하지 않고 webp 만 사용해야 할까요?? 그렇지는 않습니다.

 

Next.js 서버의 이미지 최적화 모듈은 요청 헤더에 있는 Accept 헤더를 읽고, 이미지 형식을 최적화하여 내려주게 됩니다.

그렇기 때문에 Next.js 설정에서 AVIF 를 사용하겠다고 설정하더라도, AVIF 를 지원히지 않는 Edge 브라우저에게는 Webp format 을,

AVIF 를 지원하는 Chrome 브라우저에서는 AVIF 포맷을 응답으로 내려주기 때문에 사용하셔도 됩니다.

이미지 최적화 시점과 이미지 재사용

Next.js 는 요청이 들어왔을 때, dist 폴더 밑에 cache/images 폴더에 최적화한 이미지를 동적으로 만들고, 이후에 동일한 요청에 대해서는 이미 만들어 놓은 최적화한 이미지를 캐시로서 재사용합니다.

기본 dist 폴더인 .next 폴더 아래의 cache 폴더 아래에 이미지 관련 폴더가 없습니다

하지만 이미지를 처음 요청한 후 아래의 이미지 사진과 같이 이미지 최적화 동작이 수행되면서 이미지 관련 폴더가 생성됩니다.

 

첫 번째 요청이 끝난 후에 다시 동일한 이미지를 요청하는 경우에는 이미지 최적화 되어 있는 이미지를 재사용하기 때문에 좀 더 빠르게 응답하는 모습을 볼 수 있습니다.

 

Next.js 의 서버가 동작한 뒤에 첫 요청이 들어온 경우에는 이미지를 최적화 하는 로직이 있기 때문에 시간이 조금 더 오래 걸립니다.

 

아래 그림에서 볼 수 있듯이 첫 번째 요청은 64ms 가 걸렸지만, 두 번째 요청은 최적화한 이미지를 재사용 했기 때문에 5ms 로 빠르게 응답을 주는 모습을 보실 수 있습니다.

 

 

최적화한 이미지를 재사용했는지 여부를 Next.js 에서 추가로 전달하는 응답 헤더를 살펴보면 알 수 있습니다.

이미지가 캐시가 되어 있지 않았다면 n-Nextjs-cache 헤더에 MISS 를 반환해 주고 이미지가 캐시되어 있었다면 HIT 을 응답으로 전달하기 때문에 이 값을 보고 이미지의 캐시 여부에 대한 판단이 가능해 집니다.

 

 

 

 

 

 

 

'Next' 카테고리의 다른 글

[Next.js] 배포시 console 지우기  (1) 2024.09.20
Next.js MetaData 심화  (0) 2024.07.30
Next.js - 경로  (1) 2024.05.21
Next.js  (0) 2024.05.21