안녕하세요 :) 오늘은 nextjs에서 제공하는 image 컴포넌트의 로더 기능을 사용하여 서버 부하를 줄이게 된 경험을 공유하고자 합니다.
문제점 발견: Next/Image의 동작 방식
ec2 서버의 부하를 줄이고 속도 개선 등 여러 이점을 위해 회사 내 인프라 구조는 CDN 서버를 맨 앞에 두어 이를 거쳐가도록 되어있습니다. 하지만 한가지 골칫덩이가 있었는데요. 바로 Next/Image입니다.
기존 프론트엔드의 이미지들은 모두 Next에서 제공하는 Image 컴포넌트로 되어있었습니다.
import ImgSrc from '@/static/a/b/test.png';
<Image src={ImgSrc}/>
위와 같은 코드가 있을 때 우리는 빌드 후 아래와 같은 결과물이 나오기를 기대합니다. 그래야 s3 버킷으로 가서 원하는 경로의 이미지를 가져올 수 있기 때문이죠.
<img src="/static/a/b/test.png"/>
하지만, 실제 결과물은 아래와 같습니다.
<img src="/_next/static/media/test.008d7aa.png"/>
파일이 어떤 경로에 있었던 것과 관계없이 /_next/static/media/
로 시작하는 경로에 test.008d7aa.png
와 같이 파일명에도 hash 값을 붙이게 됩니다. 기존의 경로와 파일명이 완전히 다르게 바뀌는 셈이죠. 어떤 경로를 지정하더라도 nextjs 서버를 결국 거치게 되는 문제가 있었습니다.
그래서 저희가 처음 생각했던 해결방법은 모든 프로젝트에서 Next/Image
를 걷어내고 새로운 이미지 컴포넌트를 만드는 것이었습니다. 하지만 변경해야할 코드가 너무 많다는 점이 우려되었습니다. 그래서 혹시 NextJS에서 제공해주는 방법이 있을지 찾아보게 되었습니다. 이런 상황을 과연 고려하지 않았을까 싶었습니다.
해결책: 커스텀 이미지로더
예상은 틀리지 않았습니다. 이러한 문제를 해결하기 위해 NextJS에서는 커스텀 이미지 로더라는 것을 제공합니다.
import ImgSrc from '@/static/a/b/test.png';
const CustomImageLoader = (src) => {
const newSrc = `src 가공로직`
return newSrc;
}
<Image src={ImgSrc} loader={CustomImageLoader}/>
위처럼 커스텀 로더 함수를 이미지에 추가해주면 빌드 후 아래와 같은 결과물이 나오게 됩니다. 우리가 원하는 것이죠.
<img src="/static/a/b/test.png"/>
하나하나 작성하지 않고 적용하려면 next.config.js 파일에 아래와 같이 추가해주면 됩니다.
//custom-image-loader는 따로 작성 -> 글 하단에 링크 첨부
{
images: {
loader: 'custom',
loaderFile: './custom-image-loader.js',
}
}
추가 문제점: Next 서버없이 이미지 최적화를 할수가 없다,,?
Next 서버를 거쳤을 때의 이점은 이미지를 리사이징해서 반환해주었다는 것입니다. 예컨대 아래와 같은 이미지가 있습니다.
import ImgSrc from '@/static/a/b/test.png';
<Image src={ImgSrc} width={750}>
기존처럼 next 서버를 거치게 되면 경로는 아래와 같을 것입니다.
<img src="/_next/static/media/test.008d7aa.png?width=750"/>
경로에 ?width=750 라는 파라미터를 붙여주게 되면 next 서버에서 이미지를 750으로 잘라서 반환해줍니다. 하지만 이제 우리는 Next 서버를 거치지 않기로 했으니 이 역할을 해줄수가 없게된 것이죠.
해결책: webpack 커스텀 로더로 직접 리사이징
이 문제를 해결하기 위해 미리 이미지를 잘라놓는 방법을 택했습니다. 빌드시에 webpack에 이미지를 리사이징해주는 커스텀로더를 추가하는 것입니다. 프로젝트에서 사용된 이미지 파일을 하나하나 돌면서 저장해놓는것이죠. 모든 이미지를 자르는 것은 아닙니다. 아래처럼 예상 breakpoint를 잡아놓고 자를 필요가 있는 이미지만 잘라놓습니다.
const SCREEN_BREAK_POINT = {
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
};
예를 들어 가로가 900인 이미지가 있다면 lg나 xl은 필요가없겠죠. sm과 md 사이즈로만 미리 준비해놓는 것입니다. 결과물은 optimized라는 폴더에 저장이 되고 사용하는 쪽에서 필요한 사이즈의 이미지를 요청하게 됩니다.
import ImgSrc from '@/static/a/b/test.png';
<Image src={ImgSrc}/ width={750}>
이런 이미지가 있다면 아래 이미지 중 test.md.png를 요청하면 되겠지요?
test.sm.png //640
test.md.png //768
test.png //원본
개선 결과
이미지 로더를 적용한 이후 인프라 비용을 대폭 감소(CPU 사용량 50%, 서버 outbound 25% 감소) 시킬 수 있었습니다.
Full code
현재 위의 커스텀 이미지로더
와 웹팩 리사이징 로더
는 nextjs-image-loader라는 이름으로 배포되어있는 상태입니다.
full code가 궁금하시다면 이곳을 참고해주세요 :)
'프로젝트 개선' 카테고리의 다른 글
tailwind css에서 classname 정렬하기 (tagged template literals) (1) | 2024.10.30 |
---|---|
라이브러리 크기가 클 때 성능 개선하기 (코드 스플리팅, 부분 import) (1) | 2024.10.28 |