Next.js에서 이미지를 최적화 해보자(+Sharp.js 내부 살펴보기)

이희제·2024년 9월 25일
post-thumbnail

1. 이미지 최적화

LCP(Largest Contentful Paint) 개선을 위해 서비스에 이미지를 사용한다면, 이미지 최적화를 하는 것이 좋다.

2. 이미지 형식(포맷) 설정

이미지 형식에 따라 압축률이 달라진다.

브라우저에서 주로 지원하는 이미지 형식

  • JPG/JPEG - 가장 널리 사용되는 파일 방식, 모든 브라우저에서 지원
  • WebP - JPEG와 비교해 10~80% 압축
  • AVIF - JPEG와 Webp에 비해 파일 크기를 줄이고 이미지 품질은 유지

각 이미지 형식 별 사이즈 예시

  1. JPG/JPEG

  1. WebP

  1. AVIF

각 이미지 포맷마다 지원하는 브라우저 범위가 다르기 때문에 지원하는 브라우저에 맞게 분기처리가 필요함.

1. html에서 분기 처리 방법

picture를 활용해서 형식에 대한 분기 처리를 할 수 있음

<picture>
  <source srcset="img-url/large.avif" type="image/avif" />
  <source srcset="img-url/large.webp" type="image/webp" />
  <img src="img-url/large.jpg" alt />
</picture>

2. Next.js에서 분기 처리 방법

Next.js에서는 설정값을 통해 이미지 형식을 설정할 수 있다.

next.config.js 내에서 다음과 같이 설정할 수 있다. (공식 문서 참고)

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

→ avif가 지원되는 브라우저면 avif 형식이 적용되고 그렇지 않으면 webp 형식이 적용된다.

최신 웹 브라우저

버전 낮은 웹 브라우저(Chrome 59)

※참고 사항

  1. next/image를 사용해서 외부 이미지를 가지고 올 경우에는 next.config.js 내에 remotePatterns 설정이 필요하다.
images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        port: ''
      }
    ],
    formats: ['image/avif', 'image/webp']
  },
  1. standalone 모드로 빌드 시에 이미지 최적화 사용을 위해 sharp 모듈을 따로 설치해줘야 한다. (참고)
  • dockerfile 내 설치 스크립트 작성
  • package.json 내에 모듈 dependency 작성 후 yarn.lock 파일 생성

2024.05.08 기준으로 확인한 결과 canary 브랜치 기준 next/image는 기본으로 sharp를 통해 이미지 최적화를 하고 있고 따로 sharp 모듈을 설치할 필요 없음. (관련 PR)

추후에 stable 버전에서 기본으로 sharp를 제공할 것으로 예상됨

  1. 외부 이미지를 가지고 올 경우에서 해당 서비스 서버에서 외부 이미지 도메인 방화벽 작업을 해줘야 한다.

예시)

worker node 서버 → example.cdn.com 접근 방화벽 오픈 필요

위 3가지를 챙겨야 production 환경에서도 정상적으로 이미지 포맷을 최적화 할 수 있다.


1. next/image 사용 시 내부 이미지 최적화 코드

2. Sharp 내부 이미지 최적화 로직

jpeg (손실 압축)

→ 손실 압축이기 때문에 quality 옵션 값이 처리되는 것을 확인할 수 있음

png (비손실 압축)

  • PNG에 대해: PNG에서는 PNG-8(256색)과 PNG-24(16,777,216색), PNG-32(16,777,216색)로 나뉘는데 PNG-8에서는 256개의 칸을가진 색 팔레트를 이용한 것으로 256색까지만 표현이 가능하다. png-32 = 2의 24승 대략 1670만 컬러 (트루컬러) + alpha(8bit)가 할당이 된다.

quality 값이 있는 경우 palette 옵션이 true로 세팅되고 palette 값을 사용하게 되면 이미지는 제한된 수의 색상으로 구성된 팔레트를 사용하여 저장된다.

원본 이미지가 가진 색상보다 적은 수의 색상을 사용하게 되어 손실 압축 방식에 해당된다. (색상의 손실 발생)
원본 이미지의 복잡한 색상 정보를 필요하지 않을 때 사용될 수 있다.

→ compressionLevel은 next/image 사용시에 따로 값을 넘겨주지 않기 때문에 default 값인 6이 적용된다.

sharp를 통해 PNG를 생성하게 되면 기본적으로 PNG 출력은 픽셀당 8비트의 풀 컬러이다. 정확히 말하면 각 channel당 8비트가 할당되는 것이다. (full colour at 8 bits per pixel, 참고)

각 픽셀 별로 1, 2 or 4 bits인 indexed PNG 입력은 픽셀 별 8bit로 변환된다. palettetrue로 설정하면 indexed PNG 출력이 나온다.

참고
https://github.com/lovell/sharp/issues/3555
https://stackoverflow.com/questions/51291678/compress-image-using-sharp-in-node-js
https://dydtjr1128.github.io/image/2019/07/01/Image-compression.html

webp (손실/비손실 압축) - wiki

→ 손실/비손실 압축을 모두 지원하기 때문에 quality 값을 체크한다. 손실/비손실 압축 여부는 lossless 값 설정을 통해 선택할 수 있다.

lossless 기본값은 false이다. next/image 내에서 따로 lossless 값을 설정해주기 않기 때문에 next/image 사용 시에 손실 압축이 적용된다.

보통 quality 70까지는 품질이 저하된 것을 알기 어렵다고 한다. (참고)

avif (손실/비손실 압축) - wiki

→ webp와 마찬가지로 손실/비손실 압축 모두 지원하여 quality 값 체크한다. 손실/비손실 압축 여부는 lossless 값 설정을 통해 선택할 수 있다.

avif도 next/image 내에서 따로 lossless 설정값을 넘겨주지 않기 때문에 손실 압축이 적용된다.

손실 압축은 압축을 할수록 손실되기 때문에 quality 값을 통해서 압축률을 핸들링하는 것이다.

참고
https://en.wikipedia.org/wiki/PNG#Pixel_format
https://en.wikipedia.org/wiki/Channel_(digital_image)

profile
그냥 하자

0개의 댓글