출처: https://pixelpoint.io/blog/next-image/
Core Architecture
next/image의 주요 아키텍처는 주로 세 가지 구성 요소로 이루어진다.
- Next.js Image Component
- Image API
- Image Optimizer
React Component
- Next Image 컴포넌트의 주요 기능은 주어진 속성에 맞게 HTML 이미지 요소를 출력하고, src와 srcset 속성에 채워질 여러 url들을 구성한다.
- Next Image 컴포넌트는 아래와 같이 작성할 수 있다.
import Image from 'next/image';
export default function Page() {
return (
<Image
src="/profile.png"
width={500}
height={500}
alt="Picture of the author"
/>
);
}
- 위의 Next Image 컴포넌트가 반환하는 html img는 아래와 같다.
<img
alt="Example"
loading="lazy"
width="500"
height="500"
decoding="async"
data-nimg="1"
style="color:transparent"
srcset="/_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 1x,
/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 2x"
src="/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75"
>
- Next Image가 인코딩한 url은 w(너비)와 q(품질) 두 파라미터를 받는다. *h(높이) 속성은 없음.
/_next/image?url=/images/example.jpg&w=640&q=75
Image API
Next Image API는 다음과 같은 작업을 수행한다.
- image url, width, quality 정보를 받아들인다.
- 파라미터를 체크한다.
- 캐시 제어 정책을 결정한다.
- 이미지를 처리한다.
- 사용자의 브라우저에서 지원하는 포맷으로 이미지를 제공한다.
Image Optimizer
- 특정 조건에 따라 Sharp와 Squoosh 같은 다양한 최적화 라이브러리를 사용한다.
- Sharp는 native libvips 라이브러리를 사용하여 빠르고 효율적인 이미지 최적화 Node.js 모듈이다.
- Squoosh도 마찬가지로 Node 기반의 이미지 최적화 모듈이지만 설치가 필요 없다. 하지만 느리다.
- Squoosh는 로컬에서, Sharp는 프로덕트 환경에서 사용하는 것을 권장한다.
- 그러나 둘을 함께 사용하면 로컬 환경과 프로덕션 환경의 각 결과물에서 시각적 차이가 나타날 수 있다.
- Sharp의 압축 알고리즘은 색상 저하를 유발하기 때문에 이미지의 배경색을 페이지의 배경과 일치시킨다거나 할 때 Squoosh를 사용한 것과 다르게 나타날 수 있다.
Next Image에 대한 몇 가지 오해
1. next/image does not crop
- Next image 컴포넌트에 width와 height 속성을 제공하는 이유는 레이아웃 쉬프트를 방지하기 위한 것이지 실제 이미지를 해당 크기 만큼 자르는 것이 아니다.
- next/image의 url에는 높이 정보가 없다.
- 따라서 next/image는 원본 이미지의 종횡비(aspect ratio)를 변경하는 것은 불가능하다.
- fill 속성을 사용하는 경우에만 이미지가 늘어나거나 줄어드는 것 뿐이다.
*Tailwind CSS의 기본 전역 설정은 next/image가 레이아웃 쉬프트 문제를 해결하기 어렵게 만든다.
* 이 설정은 원본 이미지(비디오)의 종횡비를 유지한다.
img,
video {
max-width: 100%;
height: auto;
}
https://tailwindcss.com/docs/preflight#images-are-constrained-to-the-parent-width
2. Displayed image width ≠ loaded image width
- Image 컴포넌트에 전달된 width 속성은 이미지가 리사이즈되는 실제 너비를 나타내지 않는다.
- 위에서 살펴본 것처럼 width={500}를 적용하였을 때 생성된 url은 w=640으로 출력된다.
- x2 retina 버전에는 너비가 1000이 아닌 1080이 출력된다. x1 표준 버전에서 640이라면 x2 버전에서는 1280을 사용할 것이라 예상할 수 있지만 실제로는 그렇지 않다.
- next/image가 이미지를 리사이즈하는 기준은 next.config.js에 정의된 'deviceSides'와 'imageSizes' 옵션에 따라 결정된다.
- 두 옵션의 기본 값은 다음과 같다.
module.exports = {
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
- 이 기본 설정을 사용하는 것은 성능에 부정적인 영향을 미칠 수가 있다.
(*라이트하우스의 Page Speed Insights 점수가 낮게 나타날 수 있음.)
- 특히 페이지에 큰 이미지를 표시할 때 두드러진다. 만약 너비가 1250px인 이미지를 렌더링하면 실제 로드된 이미지의 너비는 1920px이 된다.
- 만약 브라우저가 x2 레티나 버전의 이미지를 사용하게 되면 실제 너비가 3840px로 리사이즈된 이미지가 로드된다.
- 이와 같은 문제는 두 옵션에 사이즈 값을 더 추가하면 해결된다.
3. Image optimization can be used without the next/image component
- next/image 컴포넌트를 사용하지 않고 Next Image API를 활용하여 직접 이미지 최적화를 구현할 수 있다.
- 외부 소스나 로컬 스토리지 등에서 이미지를 로딩하여 직접 최적화한 후 캔버스에 렌더링할 수 있다.
- OG 이미지를 최적화할 수 있다.
- <picture> 태그 기반의 컴포넌트를 만들어 더 나은 art direction를 구현 할 수 있다.
- Imgae API는 '/_next/image'의 하위 경로에 위치하며, url & w & q 파라미터를 전달 받는다.
/_next/image?url=https://example.com/test.jpg&w=640&q=75
* w 파라미터 값은 'deviceSizes' or 'imageSizes'에 설정된 값으로 적용된다.
4. Use import for local images
import Image from 'next/image';
import profileImg from './profile.jpg';
export default function Page() {
return (
<>
{/* Using absolute path */}
<Image src="/profile.png" width={500} height={500} alt="Picture of the author" />
{/* Using imported image via relative path */}
<Image src={profileImg} alt="Picture of the author" />
</>
);
}
- public 폴더에 절대 경로로 접근시 자동으로 너비/높이가 할당 되지만, 이때 Next.js는 destination server의 캐시 정책을 따라 기본 캐시 정책인 'public, max-age=31536000, immutable' 대신 30일 캐시 정책을 따른다.
- 이미지 자산을 30-day cache policy를 사용하는 것은 라이트하우스의 점수를 상당히 낮출 수 있다.
5. Understanding Sizes and the 100vw Technique
- next/image의 sizes 속성은 html img의 sizes 속성과 유사하나, 몇 가지 독특한 작업을 추가로 수행한다.
- 'sizes' 속성은 'srcset' 속성과 함께 작동하며, 이미지가 활성화돼야 하는 브라우저 조건과 이미지 너비 리스트를 받는다.
<img srcset="/img/html/vangogh-sm.jpg 120w,
/img/html/vangogh.jpg 193w,
/img/html/vangogh-lg.jpg 278w"
sizes="(max-width: 710px) 120px,
(max-width: 991px) 193px,
278px">
*참고: https://www.dofactory.com/html/img/sizes
- 만약 sizes 속성을 지정하지 않는다면 next/image는 아래와 같이 srcset에 standard version(x1)과 Retina version(x2)의 두 url을 생성한다.
- 브라우저는 디바이스의 pixel density(window.devicePixelRatio)에 따라 이미지를 선택한다.
- Retina device의 브라우저는 다른 조건에 상관 없이 retina version의 이미지를 선택한다.
- 만약 레티나 데스크톱에서 사용하는 이미지가 모바일이나 태블릿보다 크기가 작아도 항상 더 큰 이미지 버전(x2)을 선택하게 된다. 이는 최적화되지 않은 성능으로 낮은 라이트하우스 점수를 초래할 수 있다.
<img
srcset="
/_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 1x,
/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 2x
"
/>
- 아래와 같이 srcset url에 1x, 2x 대신 이미지의 너비를 파라미터로 지정하면, 브라우저는 너비에 기반하여 적절한 이미지를 로드한다.
- 만약 모바일 이미지의 너비가 600px(레티나 1200px)인 경우에는 1080w 버전을 선택한다.
- 만약 데스크톱 이미지가 300px(레티나 600px) 사이즈만 사용하는 경우에는 640w 버전을 선택한다.
<img
srcset="
/_next/image?url=%2Fimages%2Fexample.jpg&w=640&q=75 640w,
/_next/image?url=%2Fimages%2Fexample.jpg&w=1080&q=75 1080w
"
/>
- 즉, 현재 화면 크기에 가장 적합한 이미지를 로딩함으로써 성능을 향상시킨다. (반응형 이미지)
- next/image 컴포넌트에 sizes 속성을 추가하면 자동으로 너비에 따른 srcset url을 생성하는데, 이는 'deviceSides'와 'imageSizes' 두 옵션에 설정된 값에 의해 결정된다.
- 기본 값이 각각 8개로 설정돼 있으므로 만약 sizes 속성에 non-vw (px 등) 단위를 사용하면 총 16개의 url이 srcset에 포함된다.
- 만약 vw 단위를 사용하면 next/image는 이미지 선택의 기준을 장치 너비에 대한 상대적인 크기로 하기 때문에 'deviceSides'의 가장 작은 값(default: 640px)에 해당 비율을 곱한 것보다 큰 사이즈의 이미지 url만 추가한다.
- 예를 들어 sizes='100vw'인 경우에는 아래와 같이 'deviceSides'의 속성만 포함하는 8개의 url이 생성된다. (100vw는 곧 장치의 너비를 의미하고, 'imageSizes'는 항상 장치의 너비보다 작을 것이기 때문에 포함시키지 않는다)
<img
sizes="100vw"
srcset="
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=640&q=75 640w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=750&q=75 750w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=828&q=75 828w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1080&q=75 1080w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1200&q=75 1200w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=1920&q=75 1920w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=2048&q=75 2048w,
/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=3840&q=75 3840w
"
src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fexample.6be618a3.jpg&w=3840&q=75"
/>
- 필자는 불필요한 옵션으로 HTML을 부풀리는 것을 선호하지 않기에 이상적으로 4개의 url (x0.25, x0.5, x1, x2)을 생성하는 것을 권장한다.