Background
next.js에서 <img> 태그를 사용하다 보면 next/image를 사용하라는 권고사항이 뜹니다. 이미지 최적화가 되고, 캐싱이 된다는 것에 next/image를 사용하기는 했지만, 개발하다보니 이미지 리사이징에 대한 이슈가 생겼고 어떻게 최적화가 이루어지는지에 대한 정확한 이해가 필요했습니다.
What is next/image?
간략히 설명하면 next.js 내에서 <img> 태그 대신에 사용할 수 있는 내장 모듈입니다. 간단한 사용법은 아래와 같습니다.
import Image from 'next/image'
const MyImage = (props) => {
return (
<Image
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
Why use next/image?
기본적으로 이미지를 최적화하려면 많은 노력이 필요한데요, next/image를 사용하면 아래와 같은 이점들이 있습니다.
- 기본적으로 이미지를 webp 형식으로 변환/제공하기 때문에 이미지 품질은 유지하면서 용량을 줄일 수 있습니다.(webp를 지원하지 않는 브라우저에선 next/image가 내부적으로 처리해서 제공합니다)
- 기본적으로 이미지 크기를 리사이징해서 제공하기 때문에 작은 기기에 큰 이미지가 전달되는 것을 방지할 수 있습니다.
- 기본적으로 lazy loading을 지원하기 때문에 페이지 로딩 속도를 개선할 수 있습니다.
- 기본적으로 이미지 캐싱을 해주고, 설정을 통해 제어할 수도 있습니다.
- 선택적으로 자동 스켈레톤 UI를 생성할 수 있습니다(placeholder).
- 선택적으로 next.config.js에 지정한 origin에서만 이미지를 가져오도록 할 수 있기 때문에 악의적인 유저로부터 사용자를 보호할 수 있습니다.
- 더 알아보려면 아래 링크를 참고해주세요
next/image props
next 13 버전의 이미지 컴포넌트를 들여다보니 아래 사진과 같은 props들이 있네요. 필수 props와 옵셔널(?) props를 구분해서 봐주세요. deprecated 항목은 버전이 올라가면서 변경된 props들이니 사용을 지양해주세요.

그럼 하나씩 살펴 볼까요?
src
이미지의 소스 url을 지정합니다.
alt
이미지 대체 텍스트를 지정합니다.
width?
이미지의 가로 사이즈를 px 단위로 지정합니다(%와 같은 값은 사용할 수 없습니다).
fill 옵션이 false(default)이면 필수입니다.
height?
이미지의 세로 사이즈를 px 단위로 지정합니다(%와 같은 값은 사용할 수 없습니다).
fill 옵션이 false(default)이면 필수입니다.
fill?
width를 지정하는 대신, 이미지가 상위 요소를 채우도록 하는 boolean입니다. true로 하면 이미지에 아래와 같은 css 값이 설정됩니다. 단, fill을 true로 설정하면 sizes attribute가 100vw로 설정되기 때문에 불필요하게 큰 이미지가 로드될 수 있습니다.
- position: absolute
- object-fit: contain
- cover로 변경하려면 아래와 같이 style로 지정해주면 됩니다. (css의 !important로도 변경도 가능합니다.)
- <Image ... style={{ objectFit: 'cover' }} />
- 부모 요소에 'overflow: hidden'을 추가로 지정해주어야 합니다.
- cover로 변경하려면 아래와 같이 style로 지정해주면 됩니다. (css의 !important로도 변경도 가능합니다.)
- 따라서, 부모 요소가 'position: relative'이어야 정상적으로 동작합니다.
loader?
사용자 지정 src url 문자열을 반환하는 함수를 지정합니다. 코드를 보시면 이해하실 수 있을 거예요.
import Image from 'next/image'
const myLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
const MyImage = (props) => {
return (
<Image
loader={myLoader}
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
quality?
이미지의 품질을 설정합니다. 값은 1-100까지 설정 가능하며, 100은 최상의 품질이므로 가장 큰 용량의 이미지로 설정됩니다. 기본값은 75입니다.
priority?
true로 설정하면 이미지의 pre loading에서 높은 우선순위를 가지도록 설정할 수 있습니다. true로 설정하면 이 이미지에 대한 lazy loading이 비활성화 됩니다. 기본값은 false입니다.
loading?
이미지의 로드 동작을 설정합니다. 기본값은 lazy이며, eager로 설정하면 페이지를 로딩할 때 즉시 이미지를 로드합니다.
placeholder?
이미지가 로드되는 동안 이미지를 어떻게 보여줄지 설정합니다. 기본값은 empty이며, blur로 설정하면 이미지를 로드할 때 대체 이미지를 설정할 수 있습니다. blur를 사용하려면 blurDataURL에 이미지 소스 url을 지정해주어야 합니다.
unoptimized?
true로 설정하면 이미지의 품질, 크기를 변환하는 대신 그대로 사용합니다. 기본값은 false입니다.
onLoadingComplete?
이미지가 로딩되었을 때의 콜백 함수를 지정합니다.
How works image caching in next/image
next/image는 원본 이미지를 최적화해서 '<distDir>/cache/images' 디렉터리에 저장하고, 캐싱 시간이 만료될 때까지 최적화된 이미지 파일을 제공합니다.
캐싱 상태를 확인하려면 응답 해더 중 x-nextjs-cache 항목을 통해 이미지의 캐싱 상태를 확인할 수 있습니다.
- MISS - 캐시에 없는 이미지(최초 방문 시 최대 한 번 발생)
- STALE - 캐시가 만료되어 이전에 캐싱된 이미지를 우선 제공하고, 백그라운드에서 캐시를 업데이트 합니다.
- HIT - 캐시에 이미지가 있고, 만료되지 않았습니다.
만료 시간은 next.config.js에 설정된 minimumCacheTTL 또는 업스트림 Cache-Control 헤더의 값(max-age) 중 더 큰 값을 사용합니다. s-maxage와 max-age가 모두 있으면 s-maxage의 값이 사용됩니다.
Cache-Control 헤더가 없거나, 값이 매우 낮은 경우엔 next.config.js에 minimumCacheTTL을 설정해서 캐시 시간을 늘릴 수 있습니다.
How works image optimization in next/image
페이지에 최초로 방문한 사용자가 이미지를 요청하면, next/image는 기기 크기별로 이미지를 webp로 변환해 생성합니다. 이렇게 생성된 이미지를 srcset에 미리 지정해두고, 기기에 맞는 이미지를 로드할 수 있게 해줍니다.
- <Image>에 width, height를 설정한 경우, 1x, 2x 두 개의 이미지를 생성하고 이를 srcset에 설정합니다.
- fill=true를 설정한 경우, srcSet은 next.config.js의 images.imageSizes와 images.deviceSizes에 의해 지정됩니다. 아래는 디폴트 값이고 직접 추가해서 사용할 수 있습니다.
module.exports = {
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
- 예를 들어 위 설정의 경우, 700px 너비의 기기가 뷰포트를 가득 채우는 이미지를 요청하면, 너비 640px의 이미지를 제공합니다.
- 640px 너비의 기기에서, 100px 크기의 div 안에서 이미지를 요청하면 96px의 이미지를 제공합니다.
- 결론적으로 fill=true를 사용할 경우, 16개의 이미지를 미리 생성해서 srcset에 미리 설정하는 것을 알 수 있습니다.
위와 같이 최적화를 자동으로 해주지만, 페이지에 최초로 방문한 사용자는 이미지를 변환하고 생성하는 동안 좀 더 오래 기다려야 합니다.
domain settings for next/image
next.config.js에 아래와 같이 domains를 설정해주면 됩니다.
module.exports = {
images: {
domains: ['localhost', 'cdn.exmaple.com'],
},
};
Conclusion
이렇게 next/image에 대해 알아봤습니다. 사용하기는 간편하고 얻는 이점이 많지만, 실제로 사용하다보면 스타일링 할 때 불편한 점도 많았습니다. fill을 true로 설정하면 div wrapper가 필수인데다 srcset이 많이 생성되고.. 사용하지 않자니 width, height를 px값으로 입력해야 하기 때문에 자동적으로 너비를 계산해주기도 어려웠네요. 그래도 얻는 이점이 많으니 타협해서 잘 사용해야겠습니다.
Reference
- https://nextjs.org/docs/api-reference/next/image
- https://nextjs.org/docs/basic-features/image-optimization
- https://fe-developers.kakaoent.com/2022/220714-next-image/
- https://heropy.blog/2019/06/16/html-img-srcset-and-sizes/
'JavaScript > NextJS' 카테고리의 다른 글
[NextJS] OAuth 2.0 구글 로그인 API 적용하기 (0) | 2023.06.05 |
---|---|
NextJS - FFmpeg.wasm으로 영상 용량 줄이기 (0) | 2023.03.26 |
HttpOnly 쿠키를 활용한 JWT 로그인 구현 (0) | 2023.02.04 |
NextJS 백그라운드 서버 시작 (0) | 2022.12.28 |
NextJS - 비인증일 때 로그인 페이지로 리디렉션 (0) | 2022.12.06 |