기본 img 태그를 Image 태그로 전환하기 를 처리하면서 겪은 내용입니다.
Next/Image를 이용하여 이미지를 최적화 해보기
eslint-disable @next/next/no-img-element 없애기
현재는 3개의 페이지에서만 이미지를 불러와서 사용합니다.
3개의 페이지에서 이미지를 불러와서 사용하고 있지만, 메인 페이지의 이미지가 중복되어 사용되는 경우가 많았습니다.
따라서 우선 메인페이지에 적용되는 이미지들만 Next/Image
태그로 교체해보려고 합니다.
// 이미지의 저작권 문제때문에 src를 임의의 링크로 바꾸었습니다.
<div>
<img
className="rounded-md h-36 md:w-[280px] md:h-[280px]"
alt="racket"
src="racket.image.com/racket1.jpg"
/>
</div>
다음과 같은 img 태그를 Next/Image
태그로 바꿔보겠습니다.
<div>
<Image
className="rounded-md h-36 md:w-[280px] md:h-[280px]"
alt="racket"
src="https://racket.image.com/racket1.jpg"
/>
</div>
단순히 태그만 바꾸게되면 에러가 발생합니다. 아래 에러를 해결하기 위해 width
와 height
속성을 작성해 줍시다.
<div>
<Image
width={280}
height={280}
className="rounded-md h-36 md:w-[280px] md:h-[280px]"
alt="racket"
src="https://racket.image.com/racket1.jpg"
/>
</div>
이렇게 작성하고, 다시 실행해보면 또 다른 오류가 나타납니다.
공식 문서에도 잘 나와있듯이, 외부 이미지를 가져다 쓰기 위해서는 next.config.js
에 domain을 등록해야합니다.
const nextConfig = {
...,
images: {
domains: ["racket.image.com"],
},
};
그렇다면 결과는?
뭐야 이거 왜이래
next 앱을 띄우고 있는 콘솔에 다음과 같은 로그가 뜨는 걸 볼 수 있습니다.
upstream image response failed for https://racket.image.com/racket1.jpg 403
네트워크 탭으로 이동해서도 문제를 보도록 합니다.
"url" parameter is valid but upstream response is invalid
결론부터 말하자면, 왜 그런지는 아직도 모르겠습니다.
https://racket.image.com/racket1.jpg
해당 url을 직접 브라우저에 입력하는 경우는 이미지를 제대로 보여줍니다.Next/Image
를 사용하는 경우에는 다음과 같이 요청을 합니다.http://localhost:3000/_next/image?url=https://racket.image.com/racket1.jpg&w=384&q=75
이 문제에 대해 여러 문서들을 찾아보았지만, 마땅한 해결책은 없었습니다.
(있다면 알려주세요 🤣)
메인 페이지에서는 이미지를 사용하는 부분이 하나 더 있습니다.
바로 Youtube 비디오 리스트를 불러오는 부분입니다.
// YoutubeVideo.tsx
<div className="flex justify-center">
<img
width={320}
height={180}
className="object-cover"
src={snippet.thumbnails.medium.url}
alt="thumbnail"
/>
</div>
Youtube data api를 통해 배드민턴을 검색했을 때 나오는 상위 10개의 목록을 반환받습니다. 데이터는 thumbnail 이미지의 링크가 포함되어 있습니다.
이미 width와 height를 정의해 놓은 상태이므로, next.config.js에서 domain만 추가해보도록 하겠습니다.
const nextConfig = {
...,
images: {
domains: ["i.ytimg.com"],
},
};
// YoutubeVideo.tsx
<div className="flex justify-center">
<Image
width={320}
height={180}
className="object-cover"
src={snippet.thumbnails.medium.url}
alt="thumbnail"
/>
</div>
결과는?
아주 잘 동작합니다. 왜? 뭐가 다른건데
카카오 테크 블로그 글을 보셨다면 Next/Image
를 적용하면 얻을 수 있는 장점을 아래와 같이 소개합니다.
img 사용 시
아래 보이는 내용에서 mqdefault
가 youtube thumbnail입니다.
10개의 thumbnail을 한 번에 요청하는게 보이시나요?
메인 페이지의 youtube 비디오 리스트는 캐러셀 형태로 되어있기 때문에, 사용자에게 한 번에 보여지는 최대 비디오 개수는 4개입니다.
따라서, 처음에는 4개의 이미지만 요청하도록 바꾸고 싶습니다.
사실 Next/Image 를 사용하지 않더라도, img 속성에 loading="lazy"
를 붙여주면 원하는대로 동작합니다.
아무튼, Image 태그를 적용한 후의 결과를 보겠습니다.
캐러셀이 움직일 때마다, 그때 그때 필요한 이미지를 요청하는 걸 볼 수 있습니다.
근데 왜 위에 이미지는 한번에 다 불러오니? 이게 또 문제네
img 사용 시
img
태그를 사용하는 경우 jpeg 형식으로 이미지를 받게됩니다. 이미지의 크기는 각각 다르지만 평균적으로 약 15Kb 정도 됩니다.
Image 사용 시
Image
태그를 사용하는 경우 webp 형식을 이미지를 받아오게 됩니다. 평균 약 9.5Kb 의 크기인 것을 볼 수 있습니다.
Image 태그를 사용하는 경우, 이미지의 크기가 img 태그를 사용하는 경우의 63% 수준으로 낮아진 것을 볼 수 있습니다.
솔직히 우리가 사용하는 네트워크 속도를 생각해보면, 이 정도는 무시할만한 수준이 아닌가... 속으로 생각했습니다.
고해상도의 이미지 혹은, 이미지 의존도가 높은 페이지의 경우에는 어마어마한 성능을 낼 것으로 보입니다.
실제로 카카오 테크 블로그 에서 개선한 내용을 보았을 때, 그 효과는 어마무시했습니다.
사용자 경험의 향상을 위해 placeholder를 설정할 수 있습니다.
정적 파일의 경우 단순히 blur 옵션을 주는 것으로 해결이 가능합니다.
하지만 youtube thumbnail은 동적으로 받아오기 때문에 blur 옵션과 동시에 blurDataUrl도 같이 넘겨주어야 합니다.
Next 공식문서에는 plaiceholder를 이용하면 된다고 합니다.
근데 제 경우에는 크게 의미가 있을까 싶기도 한게, 단순히 thumbnail을 요청하는 것이 아니라, 다른 여러 정보들도 같이 불러오는 과정에서 thumbnail이 포함되는 거라...
이 부분은 이미지를 blur 하는 것보다, 다른 placeholder 이미지를 구하는게 나아보입니다.
절망편 문제를 해결하기 위해, 생각을 바꿨습니다.
아니, 그 라켓 이미지를 저장해가지고 말이야, 어? 그거 S3에 올려서 쓰면 되는거 아냐?
하지만 예상치 못한 부분에서, 근본적인 문제 해결이 불가능했습니다.
킹작권... 은 인정이지...
그래서 제가 직접 그림을 그려서 S3에 올리는 것으로 쇼부를 봐야겠네요.
사실 아까전에도 슬쩍 나왔지만, S3에 올려서 이미지를 받아오는데 성공했습니다.
하지만, 글을 쓰다보니 lazy loading
도 아닌 early more loading
을 하고있길래, 문제를 해결하고 나면 이 글을 수정하는 것으로 마무리 될 것 같습니다.
오늘의 개발 끝!