최적화 적용(4) - Next Image 적용
📌시작하며
이 글은 아래와 같이 이어집니다.
Next/Image를 다 잘 활용해보고자 이 전 포스팅에서 Next/Image 공식문서를 읽고, 활용법에 대해 복습했었다. 이후 포스팅을 바탕으로 이미지 최적화를 위해 몇 가지 리팩토링을 진행했고, 공식문서를 읽었을 때 헷갈렸던 부분을 되짚고 넘어갈 수 있었다. 이번 포스팅에서는 어떤 점을 잘못 이해했었는지, 어떻게 최적화 했는지 정리하고자 한다.
✅Properly size images
Lighthouse 검사 중 Properly size images Potential savings of 25 KiB라는 안내를 보게 되었다. 당시에 나는 Image 컴포넌트에 (내 생각엔)적절한 이미지 사이즈를 할당했고, DeviceSizes와 ImageSizes도 적절히 값을 설정했다고 생각했기에 이 안내를 이해하기 어려웠다.🤔
그러다 Network 탭에서 image가 불러와지는 것을 확인했더니, 나는 분명 이미지 사이즈를 할당할 때 400px
을 할당했는데 width가 640인 이미지가 불러와지고 있는걸 발견했다!
Lighthouse의 안내는 여기서 발생되는 것이라고 판단했으나 다시 의문에 빠졌다. 도대체 640은 어디서 나온 숫자인것인가…? 🤔 일단 Image 컴포넌트에 작성한 숫자는 아니었지만, 600도 아니고 640인 이유가 있을 것이라 생각해 작성했던 코드와 config 파일들을 다시 꼼꼼하게 확인했다.
그리고 범인(?)을 발견했는데, 바로 아래의 코드다. 나는 breakpoint의 3가지의 사이즈를 deviceSizes에 작성하며 640이란 숫자를 작성했었다.
1
2
3
4
5
//next.config.mjs
images: {
imageSizes: [300, 700, 400],
deviceSizes: [640, 768, 1024],
}
그리고! 이 코드와 함께 640의 이미지를 불러일으킨 직접적인 범인이 있었으니… 바로 Image 컴포넌트의 sizes 부분이다.
1
2
3
4
5
6
7
8
//musicCard.tsx
<Image
src={kothumbnail}
fill={true}
sizes="(min-width: 1024px) 400px, (min-width: 640px) 600px, 100vw"
alt={kotitle + "앨범 이미지"}
priority={true}
/>
내가 기존에 작성한 코드를 보면 400px
과 같이 px 기준으로 작성하고 있는데 이 부분이 문제를 일으킨 것이었다. 스택 오버플로우를 찾아보니, 이렇게 고정 픽셀로 지정하는 것이 아니라, 미디어 쿼리와 유사한 형식으로 작성해야 함을 알게 되었다.🥲
나는 해당 값을 지정할 때, 정확한 vw 값을 지정하지 못해 의도치 않게 더 큰 이미지를 불러오는 것 보단, 검사 도구를 이용해 각 사이즈 별 제일 큰 사이즈를 px 단위로 지정하는게 더 좋다고 생각했는데, 이 부분이 오히려 문제였던 것이었다.
그래서 다음과 같이 두 파일의 코드를 수정해주었다.
1
2
3
4
5
//next.config.mjs
images: {
imageSizes: [300, 400, 580, 705],
deviceSizes: [640, 768, 1024],
}
1
2
3
4
5
6
7
8
//musicCard.tsx
<Image
src={kothumbnail}
sizes="(min-width: 1024px) 20vw, (min-width: 640px) 80vw, 95vw"
alt={kotitle + "앨범 이미지"}
priority={true}
fill={true}
/>
그리고 재검사를 수행하자, Properly size images Potential savings of 25 KiB 안내가 사라졌고, Network 탭에서 확인한 image도 400px의 적절한 사이즈가 불러와져 있었다.
✅ imageSizes와 deviceSizes
이 문제를 해결하면서 내가 오해하고 있던 부분을 추가적으로 알게되었는데, 사실 나는 아래와 같이 imageSizes와 deviceSizes를 작성하면서 각 작성 된 값이 1:1로 대응하는 것이라고 생각했었다… 🥹
1
2
3
4
5
//next.config.mjs
images: {
imageSizes: [300, 700, 400],
deviceSizes: [640, 768, 1024],
}
그러니까, deviceSizes별 기본적으로 imageSizes를 지정하는 것으로 오해해서, 640px에는 300px 이미지가 기본으로 불러와지고, 768px 일땐 700px이 1024px일땐 400px 불러와진다고 생각했던 것이었다! 하지만 이 부분은 명백한 잘못된 해석이었다… 😅
먼저 위와 같이 코드를 수정하고, img 태그로 변환 된 부분을 확인해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<img
alt="테스트 앨범 이미지"
fetchpriority="high"
decoding="async"
data-nimg="fill"
sizes="(min-width: 1024px) 20vw, (min-width: 640px) 80vw, 95vw"
srcset="
/_next/image?url=테스트이미지경로&w=300&q=75 300w,
/_next/image?url=테스트이미지경로&w=400&q=75 400w,
/_next/image?url=테스트이미지경로&w=580&q=75 580w,
/_next/image?url=테스트이미지경로&w=640&q=75 640w,
/_next/image?url=테스트이미지경로&w=705&q=75 705w,
/_next/image?url=테스트이미지경로&w=768&q=75 768w,
/_next/image?url=테스트이미지경로&w=1024&q=75 1024w
"
src="/_next/image?url=테스트이미지경로&w=1024&q=75"
style="position: absolute; height: 100%; width: 100%; inset: 0px; color: transparent;"
/>
그리고 여기서 srcset 부분의 w=(숫자) 부분을 자세히 보자. 기시감이 느껴진다.🤨 300, 400, 580, 640, 705, 768, 1024…
1
2
3
4
5
//next.config.mjs
images: {
imageSizes: [300, 400, 580, 705],
deviceSizes: [640, 768, 1024],
}
그렇다. 바로 여기에 작성한 숫자들이 이미지 크기가 되는 것이다! 🤩 그리고 이 사실을 알게 되고 드디어 imageSizes와 deviceSizes가 이해가 가기 시작했다.
1
2
3
4
5
6
7
8
//musicCard.tsx
<Image
src={kothumbnail}
sizes="(min-width: 1024px) 20vw, (min-width: 640px) 80vw, 95vw"
alt={kotitle + "앨범 이미지"}
priority={true}
fill={true}
/>
다시 처음부터 살펴보면 위와 같이 sizes를 미디어 쿼리처럼 작성하면, 뷰포트가 1024px 이상인 경우 Next/Image는 20vw에 해당하는 크기의 이미지를 사용하고자 하고, 이때 사용자가 작성한 (혹은 default값으로 작성된) imageSizes와 deviceSizes를 확인해서 가장 적절한 사이즈를 사용하게 되는 것이다!
한 번 더 해보면, 640px 이상인 경우에는 80vw에 해당하는 이미지 사이즈를 표시하려고 하고, 가장 적절한 값을 찾고자 사용자가 imageSizes와 deviceSizes를 확인하여, 미리 지정한 사이즈들 중 (640*0.8 = 512) 512px과 가장 가까운 ‘큰 값(580)’ 을 선택해 이미지를 표시하는 것이다.
이를 통해 반응형 작업을 할 때 1024px 이상일 때와 640px 이하일 때, 각 사이즈 별로 컴포넌트 사이즈도 달라질 때 가장 적절한 최적화 된 이미지를 보여줄 수 있다!
✅ priority
지금 프로젝트에서 Lighthouse 점수를 제일 많이 깎는건 Largest Contentful Paint element에서 Load Delay 부분이다. 이 부분을 어떻게 해결하면 좋을지 찾다가, priority에 관해 다시 이해한 부분이 있어서 정리해보고자 한다.
priority={true}
를 설정하면, 해당 이미지는 우선순위가 높아져, 미리 로드된다. 이 때, lazy loading은 자동으로 비활성화 된다.
- LCP 요소로 감지된 이미지에 사용해야 한다.
- 뷰포트 크기에 따라 여러 우선순위 이미지를 가질 수 있다.
- 화면 상단에 보이는 이미지에만 사용한다.
- 기본값은 false다.
공식문서의 이러한 설명은 지금 내가 만드는 컴포넌트에 적합하기 때문에 priority를 true로 설정하는 것이 적절해 해당 property를 유지했다.
➡️lazy Loading vs priority
사실 lazy loading도 최적화 방법중 하나이기 때문에, priority를 설정하는게 좋은건가? 라고 생각해서 빼보기도 하고 넣어보기도 하며 Lighthouse를 검사했을 때 확실히 priority를 설정하는 쪽이 좋았다.
그래서 두 가지의 차이점을 정리해보고자 한다.
특징 | Lazy Loading | Priority 속성 |
---|---|---|
차이점 | 사용자에게 이미지가 보여질 때까지 로딩을 지연시켜 초기 페이지 로딩 시간을 줄이는 기법 | 중요한 이미지를 미리 로드해 페이지의 주요 콘텐츠가 더 빨리 표시되도록 하는 기법 |
장점 | - 초기 페이지 로딩 속도 개선 - 네트워크 자원 절약 | - 중요한 콘텐츠 빠르게 표시 - LCP 최적화 |
단점 | - 이미지 로드의 지연 발생 가능 - SEO에 부정적인 영향 가능성 | - 초기 로딩 시간 증가 - 네트워크 자원 소모 |
사용 적합성 | 스크롤을 많이 사용하는 페이지, 콘텐츠가 많은 페이지에 적합 | 초기 로딩 시간보다 중요한 콘텐츠의 빠른 표시가 중요한 경우에 적합 |
📩마무리
프로젝트에 적용해보면서 내가 잘못 이해하고 있던 것을 깨닫고 다시 수정해 나가면서, Next/Image의 최적화 방식과 편리함에 대해 감탄하기도 했고, 역시 뭐든 직접 해봐야지 단순히 글만 읽는것으로는 내 것으로 만들었다가 어렵다는 걸 느끼게 되었다.😎