-
[포트폴리오] 포트폴리오 웹사이트 최적화 - 1 (Properly size images, Serve images in next-gen format)Next.js 2024. 2. 7. 08:00728x90반응형
라이트하우스 검사 결과에서 나온 퍼포먼스 항목을 하나씩 고쳐보고자 한다
1. Properly size images페이지에서 사용자 화면에 렌더링된 사이즈보다 큰 이미지를 제공하면 쓸데없는 용량이 낭비되고 페이지 로드 시간이 느려진다고 한다(참고)
즉, 실제로 화면에 렌더링되어야 할 크기보다 실제 이미지의 크기가 너무 크면 느려지고 낭비니까 고치라는 항목이었다캐러셀에 있는 프로젝트 이미지1,2와 로고 이미지에 대해서 적절한 사이즈를 주어야했다
살펴보니 로고의 경우 렌더된 사이즈는 최대 40*40인데 실제 사이즈는 200*200이었다
프로젝트 이미지들도 실제로 렌더링되어야하는 사이즈에 비해 컸다
밑에 작성할 Serve images in next-gen formats 항목과 같이 해결하기 위해 Next.js의 Image를 사용해서 수정하였다
Next.js의 Image를 사용하면 어떤 점이 좋을까?
공홈에서는 아래와 같이 설명하고 있다
Next.js 이미지 컴포넌트는 자동 이미지 최적화를 위한 기능으로 HTML <img> 요소를 확장합니다
- Size Optimization: WebP 및 AVIF와 같은 최신 이미지 형식을 사용하여 각 기기에 적합한 크기의 이미지를 자동으로 제공
- Visual Stability: 이미지가 로드될 때 레이아웃이 자동으로 이동하는 것을 방지
- Faster Page Loads: 기본 브라우저 lazy loading을 사용하여 이미지가 뷰포트에 들어올 때만 로드되며, blur-up placeholder를 선택적으로 사용
- Asset Flexibility: 원격 서버에 저장된 이미지도 on-demand 이미지 크기 조정 가능Next.js의 Image를 사용하면 알아서 최신 이미지 형식으로 변환해서 제공해주고 기본으로 lazy loading도 제공해준다
서버에 저장된 이미지 크기도 조절이 가능하고 sizes, fill, style 등의 props로 미리 이미지의 크기를 지정해놔서 레이아웃 쉬프트를 방지할 수 있다
실제로 Image를 적용하니 아래와 같이 미리 설정된 srcset과 lazy lodaing이 적용되어있는 걸 볼 수 있다
예를 들면 뷰포트가 640이면 파라미터로 보낸 w와 q(ex: w=640&q=75(기본값))을 적용해서 이미지를 보내준다는 것이다
그런데 내가 헷갈렸던게 있다...! 이미 tailwind로 반응형을 해놓았는데 왜 저 srcset이 적용되어봤자 뭔 소용일까하는 생각이었다
<divclassName="relative cursor-pointer w-[400px] h-[200px] mobile_xs:w-[280px] mobile_xs:h-[200px] mobile_sm:w-[280px] mobile_sm:h-[200px] tablet:h-[300px] tablet:w-[350px] notebook:h-[200px] notebook:w-[350px] object-cover overflow-hidden rounded-t-lg"onClick={(e) => {openModal(project);e.stopPropagation();}}><Imagesrc={project.image.url}alt={project.image.alt}sizes="100vw"fillstyle={{ objectFit: "contain" }}/>살펴보니까 Image를 감싸는 부모요소(컨테이너라고 부르겠음)의 width와 height만 반응형으로 만든 것이지
이미지 자체는 Next.js의 기본 srcset이 적용되고 있는 것이었다
그러니까 만약 뷰포트가 640이라면 이미지를 담고 있는 컨테이너는 mobile_sm이 적용되어 w: 280, h: 200이 적용되고 있었던 것이고 이미지는 640으로 다운로드되는 것이다
음 그러면 아직도 실제 렌더링되는 이미지 사이즈와 실제 이미지 사이즈간에 아직도 간격이 크기 때문에
srcset을 따로 작성해 적용해줄 필요성이 있었다
찾아보니 sizes를 미디어 쿼리처럼 브레이크 포인트를 지정해 이미지의 너비를 조절할 수 있는 거 같았다
더보기Next.js/Image의 sizes prop이란?
공식홈페이지를 살펴보니까 sizes는 미디어 쿼리랑 비슷하게 각기 다른 브레이크 포인트에 따라서 이미지가 얼마나 넓어질지를 제공한다고 한다
sizes의 값은 fill 옵션을 사용하거나 반응형이 적용된 이미지의 성능에 영향을 준다고 한다
sizes는 이미지 퍼포먼스와 관련해 아래와 같이 2가지의 중요한 목적을 제공해준다고 한다
1. 브라우저는 sizes 값을 사용해 Next.js의 Image에 자동 생성된 srcset에서 다운로드 할 이미지 크기를 결정함
sizes를 사용해 이미지가 실제로 전체화면보다 작아지도록 브라우저에 알려고, fill 속성을 사용해 이미지에 sizes 값을 지정하지 않으면 자동으로 기본 값 100vw(전체 화면 width)가 적용된다고 한다
2. 자동으로 생성된 srcset 값을 변경함
sizes 값이 없으면 고정 크기 이미지(ex: 1x/2x 등)에 작은 srcset이 자동으로 생성된다고 한다.
sizes가 정의되어 있으면 반응형 이미지(ex: 640w/750w 등)에 적합한 큰 srcset이 생성된다고 한다.
sizes 속성에 뷰포트 너비의 백분율을 나타내는 50vw와 같은 크기가 포함된 경우 너무 작아서 필요하지 않은 값이 포함되지 않도록 srcset이 잘린다고 한다
예를 들어 뷰포트의 width가 600를 초과하면 이미지의 width를 600px, 이하면 이미지의 width를 뷰포트의 50%로 지정한다고 했을 때, sizes에서 50vw를 사용했기 때문에 뷰포트의 width가 600px 이하일 때 이미지의 width가 300 이하가 될 수 있음
때문에 300w를 Next.js가 자동으로 srcset에서 삭제함
아이콘 이미지에는 width, height를 설정해주고 sizes를 설정하지 않았다
sizes를 설정하지 않았기 때문에 자동으로 1x, 2x srcset이 적용되었다<Image src={logo} alt="homepage-logo" width={40} height={40} style={{ borderRadius: "999px" }} />
그리고 rendered size(화면에 그려지는 사이즈)와 intrinsic size(실제 사이즈)를 확인해보니 내가 원하는 게 아니었다^^...
정확히는 intrinsic size가 rendered size의 2배가 되게끔 하고 싶었는데 그게 안 되었다
예를 들어 뷰포트가 360미만일 때 rendered size가 25px이고 intrinsic size가 그 두 배인 50px로 되길 원했던 것이다
왜냐면 레티나 디스플레이는 같은 고해상도 기기는 같은 공간(픽셀)에 더 많은 픽셀을 그릴 수 있기 때문에 너비 기준으로 두 배 정도 큰 이미지를 사용하는 것이 좋고 이미지를 확대할 때 선명도 손실을 최소화하기 때문이다
때문에 위의 코드를 아래와 같이 수정하였다
<Image src={logo} alt="homepage-logo" sizes="(max-width: 360px) 25px,(max-width: 768px) 30px, 40px" style={{ borderRadius: "999px" }} />
sizes를 사용해 뷰포트 360미만이면 이미지 크기를 25px, 768미만이면 30, 나머지 경우에는 40으로 설정했다
아 그리고 next.config.js에 아래와 같이 디바이스 사이즈와 이미지 사이즈를 설정했다
images: { deviceSizes: [320, 360, 768, 1024, 1280], imageSizes: [50, 60, 80], formats: ["image/avif", "image/webp"], },
디바이스사이즈가 360 아래면 이미지 사이즈가 50, 768 아래면 60, 그 이외에는 80으로 지정되게 해놓았다
그리고 개발자도구를 열어서 보니 내가 원하는대로 render size의 두 배인 값이 실제 크기로 되어있는 걸 볼 수 있었다
근데 문제는 imageSizes를 저렇게 설정해놓으면 로고에는 내가 원하는 대로 사이즈가 적용되겠지만 다른 이미지들의 사이즈는 저렇게 되길 원하지 않는다는 거다...
그리고 다른 이미지들이 훨씬 크기와 용량이 컸기 때문에 로고 이미지는 sizes 속성을 빼고 자동으로 적용되는 1x, 2x srcset을 적용하기로 했다
picture 태그도 알아보았는데 적절하지 않은 거 같아서 패스했다...
뭔가 방법이 있을 거 같은데 찾아보아야겠다 ㅠ
나머지 이미지인 프로젝트 이미지1,2에 대한 작업을 이어나가기로 했다
프로젝트 이미지 1,2는 ProjectCard, Modal 컴포넌트에 사용되었다
// ProjectCard.tsx <Image src={project.image.url} alt={project.image.alt} fill sizes="(max-width: 360px) 280px, (max-width: 768px) 350px, (max-width: 1024px) 350px, 400px" style={{ objectFit: "cover" }} />
// Modal.tsx <Image src={modalContent.image.url} alt="project-detail-image" fill sizes="(max-width: 360px) 280px, (max-width: 768px) 350px, (max-width: 1024px) 350px, 400px" style={{ objectFit: "contain", }} />
next.config.js도 아래와 같이 수정해주었다
디바이스사이즈에 따라서 sizes에 정해주었던 크기의 2배를 한 사이즈이다
그러니까 위의 Image에서 뷰포트가 360 아래면 이미지 사이즈를 280으로 지정했고 이 값의 2배인 560을 아래와 같이 설정해놓았다
images: { deviceSizes: [320, 360, 768, 1024, 1280], imageSizes: [560, 560, 700, 700, 800], formats: ["image/avif", "image/webp"], },
수정한 후 아래와 같이 intrinsic size가 변경되었다
처음 검사결과와 다르게 용량이 많이 줄어들었다...!(바로 아래에 쓰겠지만 formats 설정을 해서 AVIF로 변환되었기 때문에 더 줄어든거같다)
2. Serve images in next-gen formats최신 이미지 형식인 WebP이나 AVIF 형식을 사용하라는 거였다
살펴보니 로고이미지나 프로젝트 카드에 사용한 이미지들이 jpg였다
Next.js의 Image를 사용하면 자동으로 알아서 WebP이나 AVIF로 바꾸어주어 최적화를 해주기 때문에 이를 적용했다
네트워크 탭으로 해당 이미지를 보면 Next.js가 알아서 webp로 변환해주어 다운로드 된 것도 볼 수 있다
아니 캡쳐 무슨 일; 그런데 최근 읽은 최적화 관련 책에 AVIF 형식이 압축률이 더 좋다고 한 걸 본적이 있었다
그래서 빠르게 찾아보았다
WebP
- JPEG나 PNG보다 압축률이 좋음
- WebP는 무손실 및 손실 압축 옵션을 모두 제공
- 알파채널(투명도)지원
AVIF
AV1 비디오 코덱 기반 -> 때문에 WebP보다 훨씬 압축률이 좋음
Next.js 공홈의 설명을 보면 AVIF는 일반적으로 인코딩하는 데 20% 더 오래 걸리지만 WebP에 비해 20% 더 작게 압축된다고 한다
즉, 이미지를 처음 요청할 때는 일반적으로 속도가 느려지고 이후 캐시된 요청은 더 빨라진다는 것이다
can i use를 봐도 두 형식 다 지원하는 브라우저들이 많고, 유저가 사용하는 브라우저가 AVIF를 지원하지 않는다면 Next.js가 알아서 WebP나 다른 형식의 이미지들로 알아서 주기 때문에 나의 경우 AVIF로 설정해주기로 했다
images: { formats: ["image/avif", "image/webp"], },
next.config.js에 위와 같이 formats을 설정해주고 다시 네트워크 창을 보았더니 아래와 같이 AVIF 형식으로 이미지가 받아와지는 것을 볼 수 있다
AVIF 형식 WebP 형식 확실히 비교해보면 용량이 줄어든 것을 볼 수 있다!
그리고 라이트하우스를 다시 돌려보니 해당 항목이 없어진 걸로 보아 이 항목은 해결이 된 걸로 볼 수 있다...!
근데 Next.js는 어떻게 자동으로 이미지 형식을 최적화를 해주는 걸까?
공홈을 보니 request header의 Accept 헤더를 통해 브라우저에서 지원하는 이미지 형식을 자동으로 감지한다고 한다
Accept 헤더가 구성된 형식 중 두 개 이상의 형식과 일치하는 경우 아까 next.config.js에서 설정한 formats 배열에서 첫 번째로 일치하는 형식이 사용된다고 한다. 만약 일치하는 항목이 없거나 이미지가 애니메이션인 경우 원본 이미지의 형식으로 폴백한다고 한다..!
properly size images부분을 고치는데 sizes 부분과 tailwind에서 정의한 반응형이 헷갈렸고
그 다음 렌더 사이즈와 실제 사이즈를 조절하는 것에서 시간이 좀 오래 걸렸다
그래도 하나하나 차근차근 해나가다보니 해결할 수 있었다
사실 properly size images를 해결할 때 그냥 rendered size에 맞춰 컨버터 같은 걸 이용해 이미지를 줄일까하는 생각이 먼저 들었지만
이미지가 1억개라면...? 하나하나 일일히 다 노가다를 해서 줄여야할 것이다
때문에 최대한 알아서 조절되게끔 하고 싶었다
이게 뭐라고 이렇게 오래 걸린걸까라는 생각과 동시에 공부가 되어서 좋다는 생각이 같이 든다ㅎ
혹시라도 틀린 점이 있다면 말해주세요
참고
- https://nextjs.org/docs/app/api-reference/components/image#sizes
- https://nextjs.org/docs/pages/building-your-application/optimizing/images
- https://developer.chrome.com/docs/lighthouse/performance/uses-responsive-images?hl=ko
반응형'Next.js' 카테고리의 다른 글
[포트폴리오] 포트폴리오 웹사이트 최적화 - 2 (next/dynamic를 사용한 code splitting) (1) 2024.02.14 [포트폴리오] 포트폴리오 웹사이트 최적화 - 1 (Light House 및 수정할 부분) (2) 2024.01.30