이미지 최적화에 대해서 관심이 많은데 이와 관련된 글을 읽다가 나중에 적용해보고자 글을 쓴다.
Sharp 사용하기
Sharp의 경우에는 vercel에서 배포하면 자동적으로 깔리지만, 이전에 개발 환경에서 사용할려고 하면 사용자 본인이 깔아서 사용해야 한다.
우선 Sharp란? Sharp는 Node.js의 빠른 오픈 소스 이미지 처리 모듈이다. 압축되지 않은 이미지 데이터의 일부 영역만 메모리에 저장하므로 처리 속도가 빠르다. Sharp는 여러 이미지 형식(JPEG,PNG,GIF,Webp,Avif,SVG)과 압축되지 않은 원시 픽셀 데이터도 지원한다.
이미지 최적화
최적화를 위해 sharp와 squoosh라는 두 라이브러리를 사용한다. Next.js에서 production환경에서는 sharp를 권장하고, 유저가 따로 package.json에 추가하여 설치해야 한다. next에서는 이미지 최적화를 위하여 sharp를 설치하여 사용하는 것을 권장한다. 또한, 실 배포시에 이미지 로딩속도가 느리다면 sharp를 설치해 사용하는 것도 좋다.
추가)
추가적으로 WebP 확장자 대신 AVIF 확장자 사용한다.
Next/Image 컴포넌트를 사용하면 기본적으로 WebP 확장자로 최적화한다. WebP는 Google에서 개발한 이미지 형식으로 웹에서 이미지를 효과적으로 저장하고 전송하기 위해 사용되는데, AVIF는 더 나은 압축률과 이미지 품질을 제공하여 미래 웹 이미지 포맷의 표준으로 자리잡을 가능성이 높다. 따라서 AVIF를 사용하여 웹 이미지를 최적화하는 것이 좋을 수 있다.
이 둘은 성능에는 거의 차이가 없지만, 큰 이미지의 경우에는 그 차이가 확연히 날 것으로 보인다. 또한, OS에 따라 avif가 사용이 안 되는 곳도 존재한다. next.config.js 파일에서 아래와 같이 설정하면 webp로 사용이 가능하다.
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
},
};
layout shift
페이지 콘텐츠가 예기치 않게 이동하는 현상이다.
Next.js에서 제공하는 <Image> 컴포넌트를 사용하려면 무조건 너비와 높이를 지정해서 넘겨줘야 한다. <Image> 컴포넌트는 width, height값으로 이미지 크기를 예상하여 렌더링한다. 만약 그러지 않는다면 layout shift가 발생한다.
placeholder
Next.js의 <Image> 컴포넌트는 이미지가 로딩되기 전에 보여줄 placeholder 속성을 기본으로 제공한다. 기본값은 "empty"지만, empty로 설정되어도 width, height 값이 있으면 layout shift는 발생하지 않는 듯 하지만 공백의 화면이 이미지를 로드하기까지 보여진다.
이미지를 불러올 때 용량이 크고 로딩 시간이 오래 걸릴 경우, 페이지의 성능이 저하될 수 있다. 이 때 이미지에 placeholder를 사용하면 이미지 로딩 전에 빈 칸에 이미지의 위치와 크기를 차지하면서 로딩 시간을 줄일 수 있다. 또한, placeholder를 사용하면 사용자가 이미지를 보기 전에 이미지가 로드되기 전까지 레이아웃이 깨지는 것을 방지할 수 있으며, placeholder를 사용함으로써 페이지의 사용성을 향상시키고, 사용자 경험을 개선할 수 있다.
그래서 blur 옵션을 사용하여 정적 이미지의 경우 알아서 blur image를 보여주며 외부 이미지의 경우 blurDataURL에 지정된 base64 이미지를 지정해주어야 한다.
Plaiceholder 설치하기
이미지 placeholder를 만들어주는 라이브러리이다.
이미지를 외부 도메인이나 public 폴더에서 동적으로 불러오는 경우에는 blurDataURL을 필수적으로 작성해줘야 하는데 이 때 공식 문서에서 제안하는 것이 plaiceholder 라이브러리를 사용하라고 권장한다.
npm install sharp
npm install plaiceholder
npm install @plaiceholder/next
next.config.js에 설정 추가하기
const { withPlaiceholder } = require('@plaiceholder/next');
const nextConfig = {
// ...
}
module.exports = withPlaiceholder(nextConfig);
위와 같이 next.config.js에 설정을 추가해준다.
Plaiceholder 적용하기
위에서 Plaiceholder를 쓰기 위하여 설치해주었으니, 이를 적용해보자.
getPlaiceholder(src, options);
- src : public 기본 url을 적어주면 된다.
- options : 선택사항으로, brightness, format, hue등을 적어놓을 수 있다. size를 많이 적어놓는 것 같다.
- option을 통해 size를 지정할 수 있는데 기본 값은 4px이다.
// components/ImgWithPlaceholder.tsx
import getBase64 from "@/utils/getBase64";
import { getPlaiceholder } from "plaiceholder";
import Image from "next/image";
async function ImgWithPlaceholder({ src }: { src: string }) {
const { base64, img } = await getPlaiceholder(src, { size: 12 });
return (
<Image
src={src}
alt={src}
width={img.width}
height={img.height}
sizes="65vw"
style={{ height: "auto" }}
placeholder="blur"
blurDataURL={base64}
/>
);
}
export default ImgWithPlaceholder;
node.js의 fs 모듈을 이용해 public 폴더의 이미지를 읽어와 Buffer를 생성한다.
10픽셀의 base64URL을 만들 것이기 때문에 options에는 {size: 10}을 전달한다.
이 함수에서는 base64URL에 더불어 이미지의 높이, 너비 정보도 함께 리턴하고 있기 때문에 이 정보를 <Image> 컴포넌트에 적용하면 layout shift 문제도 함께 해결할 수 있다.
// components/ImgWithPlaceholder.tsximport getBase64 from "@/utils/getBase64";
import Image from "next/image";
async function ImgWithPlaceholder({ src }: { src: string }) {
const { base64, img } = await getBase64(src);
return (
<Image
src={src}
alt={src}
width={img.width}
height={img.height}
sizes="65vw"
style={{ height: "auto" }}
placeholder="blur"
blurDataURL={base64}
/>
);
}
export default ImgWithPlaceholder;
<ImgWithPlaceholder src={`/media/~`} />
컴포넌트로 만들었으니 위와 같이 사용이 가능하다.
👇🏻 참고
https://joy.pe.kr/nextjs-optimize-image/?utm_source=oneoneone#mutable-리소스
https://wnsdufdl.tistory.com/526