Next/Image 이미지 최적화 (cache, skeleton)

henry·2023년 5월 7일
20
post-thumbnail


작년에 진행한 프로젝트 '니콘내콘'에서 Next.js의 핵심 기능 중 하나인 Next/Image를 학습하고 적용해봤다. 이 프로젝트에서 이미지를 표시하는 데 일반적인 img태그를 사용하고 있었다.

return (
  <ItemImg
    src={thumbnail}
    width={40}
    height={40}
  />
)
            
const ItemImg = styled.img`
  width: 40px;
`;

Img태그를 사용한 이미지 화면 Light House 측정

  • 접근성 점수가 77점으로 향상의 여지가 있음을 나타냈다.
  • next/image를 사용하라는 경고메세지를 받았다.

Next js 공식문서에도 Next/iamge 컴포넌트를 권장하고 있다. 그 이유는 다음과 같다.

  1. Next/Image는 장치에 맞게 크기가 조정된 이미지와 최신 이미지 형식을 제공한다.
  2. 누적 레이아웃 변화(CLS)문제를 방지한다.
  3. 기본적으로 Lazy loading을 사용하며 이미지는 뷰포트에 나타날 때에만 로드되며, 더 나은 사용자 경험을 위해 선택적으로 blur 이미지를 제공할 수 있다.
  4. 이미지 크기 조정을 지원하며, 외부 이미지에 대해서도 동작한다.

1. Next.js 13 next/Image

Img태그와 Next/Image 를 적용한 화면의 로딩시간 비교

1. Img 태그 사용

2. Next/Image 사용

테스트는 크롬 브라우저에서 진행했다.
이미지 크기 측면에서는 img 태그와 Next/Image를 사용한 화면 간에 차이가 있었다. Next/Image를 사용하면 크롬이 WebP 이미지 형식을 지원하기 때문에 자동으로 이미지가 WebP 형식으로 최적화되는 것을 확인할 수 있었고, 이 외에도 img 태그를 사용한 화면에서 Cumulative Layout Shift (CLS) 현상이 발생하는 반면, Next/Image를 사용한 화면에서는 CLS 현상이 나타나지 않는 것을 확인할 수 있었다.

next/image를 통해 WebP 형식으로 변환된 이미지 정보는 아래 링크에서 확인할 수 있다.

Next.js 13 버전과 Next.js 12버전의 next/image 차이

// next js 13버전에서 next/image 사용
import Image from "next/image";


// next js 13버전에서 12 버전 이하의 Image를 사용하려면 Image대신 LegacyImage를 import해서 사용한다.
import LegacyImage from "next/legacy/image";

Next.js 13버전과 Next.js 12버전의 next/image는 눈으로만 보면 다른점이 없지만 개발자 도구를 확인해보면 DOM트리에서 다른 점을 확인할 수 있다.

이미지를 감싸는 span태그가 12버전에서는 존재하지만, 13 버전에서는 존재하지 않는 것을 확인할 수 있었다.

Next.js 13 버전에서는 Next/Image를 사용할 때 이미지를 감싸는 span 태그가 사라진 것을 확인할 수 있다. 이렇게 변경된 이유는 span 태그가 추가로 불필요한 DOM 요소를 생성하고, 레이아웃 계산에 대한 부하가 있어서이다.

이로 인해서 몇몇 이슈들이 해결된 것 같다

webp형식이 아닌 avif로 사용하고 싶으면 next.config.js파일에 코드 추가

images: {    
  formats: ["image/avif", "image/webp"],
},

formats옵션에서 지정한 순서대로 브라우저가 지원하는 이미지 포맷 중 우선순위를 가진다. 브라우저에서 image/avif 를 지원하면 avif로 변환되고, 지원하지 않으면 webp 로 변환이 된다. 만약 둘 다 지원하지 않으면 이미지를 jpg나 png 포맷으로 변환한다.

위에 코드를 추가해서 avif 포맷 형식으로 변환한 결과

위에 코드를 추가했지만 avif를 지원하지 않는 safari에서 테스트했을 때 avif포맷 형식이 아닌 webp포맷으로 변환이 된 것을 확인할 수 있었다.

Safari 환경

2. 캐싱

처음 데이터를 받아올 때 HTTP 응답 헤더 정보를 살펴보자

  • Cache-Control : max-age=60 : 캐시 제어를 위한 값으로, 60초 동안 캐시된다는 것을 알려준다.next/image에서는 이미지 최소 캐시 시간을 정의하는 minimumCacheTTL 속성이 있다. 기본값은 60초이며, next.config.js 파일에서 값을 설정할 수 있다.
  • minimumCacheTTL 공식문서
// next.config.js
  images: {
    domains: ["~"],
    minimumCacheTTL: 86400,
    formats: ["image/avif", "image/webp"],
  },
  • X-Vercel-Cache : 응답이 Vercel CDN 에서 제공되었으며, 캐시된 데이터가 업데이트되어야 함을 나타낸다. 요청을 처리하는 동안 캐시된 데이터를 사용하지만, 백그라운드에서 오리진 서버에서 업데이트된 내용을 가져와 캐시를 갱신한다는 의미한다.
    STALE은 캐시된 리소스가 'max-age'를 지났지만 여전히 'stale-while-revalidate' 기간 내에 있는 경우 캐시 상태는 'STALE'로 표시된다. (백그라운드에서는 캐시를 갱신하고 있다)

  • x-vercel-cache 공식문서

  • Vercel Edge Cache: Vercel의 CDN(Contents Delivery Network)에서 사용하는 캐싱 시스템으로 사용자들에게 빠르게 컨텐츠를 제공한다.

3. 스켈레톤 이미지

기존에는 스켈레톤 컴포넌트를 별도로 만들어서 이미지 로딩 전에 스켈레톤을 보여주는 방법을 사용했었다.

ex)졸업작품 판매 프로젝트에 도입했던 스켈레톤 화면
screen-recording-_5_ (1)

하지만 이 방법에는 몇 가지 단점이 있었다.

  1. 스켈레톤 컴포넌트를 위한 추가 코드 작성이 필요하다.
  2. 이미지의 크기와 비율을 제대로 반영하지 못할 수 있다.

위의 gif를 보면 게시글 상세페이지에 진입하면 좌측에는 스켈레톤 이미지가 보여지고, 이미지가 로드될 때 레이아웃이 흔들리는 Cumulative Layout Shift(CLS) 현상도 보인다.

Next/Image의 placeholder 속성 사용.

https://nextjs.org/docs/pages/api-reference/components/image#placeholder

니콘내콘 브랜드 사진은 remote 이미지를 사용하기 때문에, 빌드 타임에서 이미지 파일에 접근하는 것이 불가능하다. 따라서, placeholder로 이미지를 사용하려면 blurDataURL 속성에 base64로 인코딩된 이미지 데이터를 추가로 작성해주어야 했다.
blurDataURL에는 data:image/gif;base64 + base64로 인코딩된 이미지 픽셀 값을 설정해야 한다.

https://png-pixel.com 에서 base64로 인코딩된 이미지 픽셀을 생성할 수 있다.


// placeholder, blurDataURL 속성 설정으로 인한 Blur UI 구현

<Image
  src={thumbnail}
  width={40}
  height={40}
  alt='brand'
  placeholder="blur"
  blurDataURL="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFklEQVR42mN8//HLfwYiAOOoQvoqBAB  bWyZJf74GZgAAAABJRU5ErkJggg=="
/>

위에 2줄 데이터에만 placeholder='blur'값과 blurDataURL을 설정해서 테스트를 했고, 정상적으로 스켈레톤 UI가 적용된 것과 blur값을 적용하지 않아도 placeholder의 기본 값이 empty라서 CLS 현상이 방지되는 것을 확인할 수 있었다.

Next/Image를 사용한 Light house 성능 개선 결과

img 태그와 next/image를 사용한 버전 간의 테스트는 아래 주소에서 확인할 수 있습니다.

  1. Next.js 11 버전에서 img 태그를 사용한 주소
  1. Next.js 13 버전에서 next/image를 사용한 주소

참고

0개의 댓글