작년에 진행한 프로젝트 '니콘내콘'에서 Next.js의 핵심 기능 중 하나인 Next/Image
를 학습하고 적용해봤다. 이 프로젝트에서 이미지를 표시하는 데 일반적인 img
태그를 사용하고 있었다.
return (
<ItemImg
src={thumbnail}
width={40}
height={40}
/>
)
const ItemImg = styled.img`
width: 40px;
`;
Next js 공식문서에도 Next/iamge
컴포넌트를 권장하고 있다. 그 이유는 다음과 같다.
Next/Image
는 장치에 맞게 크기가 조정된 이미지와 최신 이미지 형식을 제공한다.- 누적 레이아웃 변화(CLS)문제를 방지한다.
- 기본적으로 Lazy loading을 사용하며 이미지는 뷰포트에 나타날 때에만 로드되며, 더 나은 사용자 경험을 위해 선택적으로 blur 이미지를 제공할 수 있다.
- 이미지 크기 조정을 지원하며, 외부 이미지에 대해서도 동작한다.
테스트는 크롬 브라우저에서 진행했다.
이미지 크기 측면에서는 img
태그와 Next/Image
를 사용한 화면 간에 차이가 있었다. Next/Image를 사용하면 크롬이 WebP 이미지 형식을 지원하기 때문에 자동으로 이미지가 WebP 형식으로 최적화되는 것을 확인할 수 있었고, 이 외에도 img 태그를 사용한 화면에서 Cumulative Layout Shift (CLS) 현상이 발생하는 반면, Next/Image를 사용한 화면에서는 CLS 현상이 나타나지 않는 것을 확인할 수 있었다.
next/image를 통해 WebP 형식으로 변환된 이미지 정보는 아래 링크에서 확인할 수 있다.
// next js 13버전에서 next/image 사용
import Image from "next/image";
// next js 13버전에서 12 버전 이하의 Image를 사용하려면 Image대신 LegacyImage를 import해서 사용한다.
import LegacyImage from "next/legacy/image";
Next.js 13버전과 Next.js 12버전의 next/image는 눈으로만 보면 다른점이 없지만 개발자 도구를 확인해보면 DOM트리
에서 다른 점을 확인할 수 있다.
이미지를 감싸는 span
태그가 12버전에서는 존재하지만, 13 버전에서는 존재하지 않는 것을 확인할 수 있었다.
Next.js 13 버전에서는 Next/Image를 사용할 때 이미지를 감싸는 span 태그가 사라진 것을 확인할 수 있다. 이렇게 변경된 이유는 span 태그가 추가로 불필요한 DOM 요소를 생성하고, 레이아웃 계산에 대한 부하가 있어서이다.
이로 인해서 몇몇 이슈들이 해결된 것 같다
webp
형식이 아닌 avif
로 사용하고 싶으면 next.config.js
파일에 코드 추가
images: {
formats: ["image/avif", "image/webp"],
},
formats
옵션에서 지정한 순서대로 브라우저가 지원하는 이미지 포맷 중 우선순위를 가진다. 브라우저에서 image/avif
를 지원하면 avif
로 변환되고, 지원하지 않으면 webp
로 변환이 된다. 만약 둘 다 지원하지 않으면 이미지를 jpg나 png 포맷으로 변환한다.
위에 코드를 추가했지만 avif
를 지원하지 않는 safari
에서 테스트했을 때 avif
포맷 형식이 아닌 webp
포맷으로 변환이 된 것을 확인할 수 있었다.
처음 데이터를 받아올 때 HTTP 응답 헤더 정보를 살펴보자
Cache-Control : max-age=60
: 캐시 제어를 위한 값으로, 60초 동안 캐시된다는 것을 알려준다.next/image
에서는 이미지 최소 캐시 시간을 정의하는 minimumCacheTTL
속성이 있다. 기본값은 60초이며, next.config.js 파일에서 값을 설정할 수 있다.// next.config.js
images: {
domains: ["~"],
minimumCacheTTL: 86400,
formats: ["image/avif", "image/webp"],
},
X-Vercel-Cache
: 응답이 Vercel CDN 에서 제공되었으며, 캐시된 데이터가 업데이트되어야 함을 나타낸다. 요청을 처리하는 동안 캐시된 데이터를 사용하지만, 백그라운드에서 오리진 서버에서 업데이트된 내용을 가져와 캐시를 갱신한다는 의미한다.
STALE
은 캐시된 리소스가 'max-age'를 지났지만 여전히 'stale-while-revalidate' 기간 내에 있는 경우 캐시 상태는 'STALE'로 표시된다. (백그라운드에서는 캐시를 갱신하고 있다)
Vercel Edge Cache
: Vercel의 CDN(Contents Delivery Network)에서 사용하는 캐싱 시스템으로 사용자들에게 빠르게 컨텐츠를 제공한다.
기존에는 스켈레톤 컴포넌트를 별도로 만들어서 이미지 로딩 전에 스켈레톤을 보여주는 방법을 사용했었다.
ex)졸업작품 판매 프로젝트에 도입했던 스켈레톤 화면
위의 gif를 보면 게시글 상세페이지에 진입하면 좌측에는 스켈레톤 이미지가 보여지고, 이미지가 로드될 때 레이아웃이 흔들리는 Cumulative Layout Shift(CLS) 현상도 보인다.
placeholder
속성 사용.https://nextjs.org/docs/pages/api-reference/components/image#placeholder
니콘내콘 브랜드 사진은 remote 이미지를 사용하기 때문에, 빌드 타임에서 이미지 파일에 접근하는 것이 불가능하다. 따라서, placeholder로 이미지를 사용하려면 blurDataURL 속성에 base64로 인코딩된 이미지 데이터를 추가로 작성해주어야 했다.
blurDataURL에는 data:image/gif;base64
+ base64로 인코딩된 이미지 픽셀 값
을 설정해야 한다.
https://png-pixel.com 에서 base64로 인코딩된 이미지 픽셀을 생성할 수 있다.
// placeholder, blurDataURL 속성 설정으로 인한 Blur UI 구현
<Image
src={thumbnail}
width={40}
height={40}
alt='brand'
placeholder="blur"
blurDataURL="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFklEQVR42mN8//HLfwYiAOOoQvoqBAB bWyZJf74GZgAAAABJRU5ErkJggg=="
/>
위에 2줄 데이터에만 placeholder='blur'값과 blurDataURL을 설정해서 테스트를 했고, 정상적으로 스켈레톤 UI가 적용된 것과 blur값을 적용하지 않아도 placeholder의 기본 값이 empty
라서 CLS 현상이 방지되는 것을 확인할 수 있었다.
img
태그와 next/image
를 사용한 버전 간의 테스트는 아래 주소에서 확인할 수 있습니다.