문제 상황
현재 진행하고 있는 프로젝트가 배포 마무리 단계에 이르렀는데, 한 가지 문제점이 존재했다.
메인 페이지의 초기 렌더링 시간이 너~무 느리다는 것이었다. 페이지에 접속해 전체 콘텐츠가 로드되기까지 거의 7초 이상이 소요됐었다.
웹에서는 next/image Image 컴포넌트를 사용해 최적화를 진행하고 있었다.
Image 컴포넌트를 사용하면, 기본적으로 이미지를 자동으로 최적화하고 크기를 조절해 더 작은 파일 크기로 로드하게끔 도와준다.
진행 프로젝트의 메인페이지를 불러오는 리소스만 36.7MB가 사용되었다. 반면 네이버의 경우 전체 메인 페이지를 불러오는 리소스는 8.9MB 정도였다.
개발사이트의 메인페이지에서는 불러오는 이미지만 12.6MB였는데, 네이버의 경우 4.1MB 정도였다.
제공하는 이미지의 사이즈와 개수의 차이도 있긴 하겠지만 차이가 거의 3배 넘었다..😇
이미지 최적화는 빌드 타임이 아닌 런타임에 요청이 들어왔을 때 최적화를 진행하므로, 최초 1회 요청은 응답이 느릴 수 있다.
Image를 통해 이미지 캐싱하기 위해 소요되는 시간이 더 길어, 캐싱전략이 오히려 독이 되었고
임시방편으로 next.config.js에 upoptimized 옵션을 주어 메인페이지 로드 시간을 단축시킬 수 있었다.
// next.config.js
images: {
unoptimized: true
}
개발이 어느정도 마무리 되면서 찝찝함과 성능최적화에 대한 욕심을 버릴 수 없었고, 무계획이었던 긴 연휴 동안 성능 최적화를 결심해 보게 되었다.
문제 원인
메인페이지에서 확인했던 하나의 최대 이미지 용량은 2,029KB였다. 😲
유저들이 메인페이지에 접속하기 위해 최대 음악 60분을 다운로드하고 있다고 생각하면 되는 걸까..
관리자 페이지에서 이미지를 업로드 할 때 따로 MB 제한을 두거나 압축을 해서 보내지 않았고,
서버에서도 따로 이미지 압축이나 리사이징을 진행하지 않았던 게 원인이었다.
이미지 압축/리사이징은 클라이언트에서도, 서버에서도 할 수 있다. 여기서 trade-off가 존재한다.
클라이언트에서 하게 된다면?
-> 유저가 입력한 원본의 파일을 저장할 수는 없겠지만, 서버의 리소스가 줄어들게 될 것이다.
서버 측에서 하게 된다면?
-> 원본의 파일을 저장하고, 이미지 압축의 정도를 유동적으로 조절할 수 있겠지만, 서버의 리소스가 낭비될 가능성이 있다.
내 경우 서버측에서 리소스를 쓰면서까지 원본의 파일을 가지고 있는것이 낭비라는 생각이 들었고,
웹 쪽에서 이미지 압축/리사이징을 진행하게 되었다.
이미지 압축/사이즈 조정
관리자 페이지에서 사용했던 이미지 압축/사이즈 라이브러리는 react-image-file-resizer을 사용해주었다.
react-image-file-resizer는 이미지 압축과 리사이징 옵션이 모두 포함되어 있다.
import Resizer from 'react-image-file-resizer'
export const resizeFile = (file: File, size: {width: number; height: number}): Promise<File> =>
new Promise((resolve) => {
Resizer.imageFileResizer(
file,
1000, // max width
1000, // max height
'JPEG',
80, // quality jpeg compression
0,
(uri) => {
resolve(uri as File)
},
'file'
)
})
결과적으로는
- 일반인이 보기에 시각적으로 큰 차이도 없었으며
- 약 1/4가량 이미지 용량을 줄일 수 있었다.
프로젝트에 적용 이후 이미지 압축, 및 리사이징 만으로 메인 페이지를 리소스를 36MB에서 -> 13.3MB로 약 2.76배 단축시켰고, 메인페이지 초기 로딩 속도를 기존 7초에서 3.5초 이내로 줄일 수 있었다🎉.
blur
이번에는 UX적으로 최적화시켜보자.
next에서는 이미지가 완전히 로딩되기 전까지 blur이미지를 보여주는 기능을 제공한다.
Base64 blur이미지를 불러오기 위해 next13 공식문서에서도 소개하는 plaiceholder 모듈을 사용해 주었다.
next13 이전에는 blur 속성만 추가하면 되었지만, 13부터는 아래 예시와 같이 blurDataUrl의 Base64 이미지를 따로 제공해줘야 한다.(정적 이미지 제외)
참고 : https://github.com/vercel/next.js/discussions/26168
import {getPlaiceholder} from 'plaiceholder'
export default async function getBase64(imageUrl: string) {
try {
const res = await fetch(imageUrl)
if (!res.ok) {
throw new Error(`Failed to fetch Image ${res.status} ${res.statusText}`)
}
const buffer = await res.arrayBuffer()
const {base64} = await getPlaiceholder(Buffer.from(buffer))
return base64
} catch (e: any) {
console.log(e.stack)
}
}
Your Next.js config must use the .mjs extension.
placeholder에서는 .mjs extention을 필수로 강제한다.
next.config 확장자를 .mjs로 바꿔준 후 `withPlaceholder`를 추가해 준다.
import withPlaiceholder from '@plaiceholder/next'
/**
* @type {import('next').NextConfig}
*/
const nextConfig = (phase, {defaultConfig}) => {
const config = {
// ...
}
return withPlaiceholder(config)
}
export default nextConfig
이후 사용 과정은 이전 정리글에서 좀더 디테일하게 정리해놨으니 필요하다면, 참고하면 좋을 것 같다.
결론
최적화와 동시에 UX 요소를 고려해 보며 lazy, eager, placeholder 등 next/image기본 속성에 대해 깊게 고민해 볼 수 있었고, 실제 적용해 볼 수 있는 좋은 경험이었다.
참고
https://oliveyoung.tech/blog/2023-06-09/nextjs-image-optimization/?ref=codenary
'Front-end' 카테고리의 다른 글
[Node.js] 개념 이해하기 (0) | 2022.07.01 |
---|