파일 사이즈가 커서 이미지가 점진적으로 로딩되는 상태로 인한 페이지가 뚝뚝 끊겨 보이는 현상을 방지하기 위해서 NextJS의 Image 태그를 적용해보려 한다.
Next/Image 컴포넌트에서 제공하는 대표적인 기능은 다음의 3가지이다.
lazy loading에 대해서 간단하게 설명하자면, 이미지 로드하는 시점을 필요할 때까지 지연시키는 기술을 의미한다. 예를 들어 스크린 밖에 있는 이미지들은 로딩을 지연시키고 스크린 안에 있는 이미지만을 로드해서, 불필요한 대역폭 사용을 줄이고 필요한 이미지만 빠르게 로드할 수 있도록 하는 것이다.
모든 브라우저에서 잘 동작하는 lazy loading을 구현하기 위해서는 Intersection Observer 혹은 scroll 이벤트를 통해 스크린에 element가 보일 때를 캐치하여 이미지를 로드하도록 구현해야 한다.(크롬 76 버전 이상부터는 img 태그에 loading="lazy" 속성을 추가하면 간단하게 적용할 수 있다.) 즉, FE 개발자라면 반드시 알아야하는 부분이다.
Next/Image를 사용하게 되면 자동으로 lazy loading이 적용된다. 물론 중요한 이미지 일부에 lazy loading을 적용하고 싶지 않은 경우 해당 기능을 끌 수도 있다. Image 컴포넌트의 priority라는 prop을 true로 설정하거나, loading prop에 eager 값을 설정하면 된다. prioty 값을 설정하는 것이 더 권장되는 방식이다.
Next/Image를 적용함으로써 100개의 이미지를 로드하던 기존 방식에서 스크린 내에 노출되는 이미지 26개만 로드되도록 변경할 수 있다. 이를 통해 기존보다 페이지 로드가 빨라진다,
기존 표시되는 영역에 비해 5배가 큰 이미지를 로드한다고 생각해보자. Next/Image는 디바이스 크기 별로 srcSet을 미리 지정해두고, 사용자의 디바이스에 맞는 이미지를 다운로드할 수 있게 지원한다. layout prop 설정에 따라 어떤 srcSet 목록이 변경된다. 또한 Next.js는 이미지를 webp와 같은 용량이 작은 포맷으로 이미지를 변환해서 제공한다.
적용 결과 디바이스 크기에 맞게 작은 이미지를 서빙하고 jpeg에서 webp 포맷으로 변경되어, 기존 스테이지에서 사용하던 500KB의 이미지가 40KB의 용량으로 1/12 이상 줄어들었다.
사용자의 디바이스에 맞는 이미지 사이즈를 만들고, 용량이 작은 webp 포맷으로 변환하는 작업은 이미지에 대한 최초 요청 시에 Next.js 서버에서 진행된다. 이후 요청에는 캐시가 만료될 때까지 캐시 된 이미지가 제공되기 때문에 첫 번째 요청보다 훨씬 빠르게 이미지를 서방할 수 있다. 캐시가 만료된 후 요청이 들어오면 오래된 이미지를 우선 제공하고, 백그라운드에서 이미지 최적화를 다시 진행한다.
이미지가 캐싱되는 기간은 next.config.js의 images.minimumCacheTTL 구성 또는 CDN에서 응답한 이미지의 Cache-Control 헤더 중 더 큰 것으로 정의된다.
페이지에 접근 시 다운로드 되는 이미지 총용량이 기존 25MB에서 589KB로 감소된 걸 확인할 수 있었다.
어떤 웹 사이트에 방문했을 때 이미지가 로드되기 전까지 영역의 높이가 0이었다가 이미지가 로드된 후 이미지만큼 영역이 늘어서 레이아웃이 흔들리는 바람에 다른 링크를 클릭하는 경험을 해본 적이 있을 것이다. 이를 CLS(Cumulative Layout Shift)라고 부른다. Next/Image는 레이아웃이 흔들리는 현상을 방지하기 위해 placeholder를 제공한다. 이미지가 로드되기 전에는 이미지 높이만큼 영역을 표시해서 이미지가 로드된 후에 레이아웃이 흔들리지 않도록 하는 것이다. placehldoer는 빈 영역 또는 blur 이미지(로컬 이미지의 경우 bulid 타임에 생성, 리모트 이미지의 경우에는 base64로 인코딩된 data url을 지정해 줘야 한다.)로 적용할 수도 있고, 커스텀하게 설정할 수도 있다.
import image from 'next/image';
import profilePic from '../public/me.png';
function Me() {
return (
<Image
src={profilePic}
alt="picture of me"
placeholder="blur"
/*
로딩 이미지의 경우 빌드 타임에 import 된 이미지 파일을 기준으로
자동으로 width, height를 지정하고, base64로 인코딩된 blur
이미지가 생성되어 별도의 작업 없이 placeholder="blur"를 사용할
수 있다.
*/
/>
)
}
// next.config.js
module.exports = {
images: {
domains: ['your-cdn-image-domain'],
},
};
리모트 이미지의 경우 Next.js 서버에서 이미지를 가지고 있는 리모트 서버에 직접 요청을 하기 때문에 모든 url에 대한 접근을 허용할 경우 악의를 가진 사용자에 의해 공격을 받은 가능성이 있다. 이를 방지하기 위해 이미지를 서벙하는 서버가 안전한 서버라는 것을 Next.js에 알려줘야 한다.
next.config.js 파일에 CDN의 host를 명시해야 한다.
import Image from 'next/image';
export default function Me() {
return <Image src="/me.png" alt="Picture of me" width={500} height={500} />;
}
리모트 이미지의 사용법은 로컬 이미지와 거의 동일하다 이미지 파일을 import 하는 대신 src에 이미지 경로를 작성하면 된다. 로컬 이미지와 달리 리모트 이미지의 경우에는 빌드 타임에 이미지 파일에 접근하는 것이 불가능하기 때문에 width, height 정보를 기입해 줘야 하고, blur 이미지도 생성되지 않는다. blur 이미지도 생성되지 않는다. 이미지가 로드되기 전 placeholder로 blue 이미지를 사용하고 싶은 경우에는 별도로 blurDataURL 속성에 base64로 인코딩된 이미지 데이터를 작성해 줘야한다.
Next/Image 컴포넌트에는 컨테이너 사이즈가 변경되었을 때 이미지 레이아웃이 어떻게 변경될지를 정할 수 있는 layout이라는 prop이 있다.