Next/Image 컴포넌트를 이용하여 이미지 최적화하기

나주엽·2024년 3월 15일
0
post-thumbnail

이 글은 NextJs 14로 구현된 앱의 이미지 최적화 과정에 대해 작성한 글입니다.

개요

앱을 배포한 후 스마트폰과 PC환경 모두에서 여러가지로 사용해보고 있던 와중에,
랜딩페이지를 접속할 때, 두 환경에서 모두 이미지 로딩에 상당한 시간이 걸리는 것을 확인했다.

이 앱은 어르신들을 주 사용자 층으로 구성된 홈페이지이고
따라서 PC와 스마트폰의 성능이 항상 최신사양이 아닐 가능성 높아서
문제가 있음을 판단하고 개선해보기로 했다.

기존의 문제점 찾기

NextJs + TailwindCSS 로 구현되어 있고 랜딩페이지에는 6개의 이미지가 있다.
6개의 이미지 모두 /public 아래에 저장되어 있고
tailwind.config.ts 에서 아래와 같이 정의하고, bg-image-1 와 같이 class 내에서 사용했다.

// tailwind.config.ts

import type { Config } from "tailwindcss";

const config = {
  darkMode: ["class"],
  content: [
   ...
  prefix: "",
  theme: {
    container: {
      ...
    },
    extend: {
      backgroundImage: {
        "image-1": "url('...')",
        "image-2": "url('...')",
        "image-3": "url('...')",
        "image-4": "url('...')",
        "image-5": "url('...')",
        "image-6": "url('...')",
	  },
      keyframes: {...},
      animation: {...},
    },
  },
  plugins: [require("tailwindcss-animate")],
} satisfies Config;

export default config;

첫 문제점은 이렇게 이미지를 불러오면 항상 원본사이즈의 이미지를 불러오기 때문에 불필요하게 큰 사이즈의 이미지를 불러온다는 점이다.

또, 이미지의 cache-control 을 할 수 없다는 것이다.
실제로, 개발자 도구를 열어서 Network 단을 열면, 처음엔 원본이미지를 불러오고,
새로고침을 해보면, 304 Not Modified 가 나타나는 것을 확인할 수 있다.

이미지들의 max-age 가 0으로 설정되어 있어서 (memory cache)가 나타나는 것이 아니라, 네트워크를 타고 해당 이미지가 변했는지 확인하는 과정이 발생한 것이다.

실제로 이미지를 불러오진 않지만 불필요한 동작이라 생각된다.

Lighthouse 로 웹사이트 성능 측정도 해보았다.
모바일과 데스크톱의 성능 측면이 각각 71점 68점이 나왔다.

개선해보기

Next/Image 적용

NextJs의 공식문서를 보면 Image Optimization 섹션을 보면 아래와 같이 써있다.

Size Optimization: Automatically serve correctly sized images for each device, using modern image formats like WebP and AVIF.

자동적으로 이미지를 기기에 맞는 사이즈로, 또, 최신형식인 WebP와 AVIF로 전달해준다고 한다.

또한 조금 내려서 확인해보면 minimumCacheTTL을 설정해 max-age 를 지정할 수 있다고 한다.

우선, 배너이미지의 경우 백그라운드 이미지로 사용하기 때문에
fill 속성을 넣어줬다. 보통의 경우 widthheight 로 너비와 높이를 설정하는데 fill 속성을 사용하면, 부모 태그에 display: "relative" | "fixed" "absolute" 를 설정하기만 하면 된다.

그리고, placeholder = 'blur' 를 설정하면, 로컬이미지 경우, 빌드시에 생성된 작은 사이즈의 이미지가 해당영역을 차지하고 있다가 이미지로딩이 끝나면 대체해준다고 한다.

placeholder 동작이 CLS(Cumulative Layout Shift)를 방지해준다고 합니다. 저는 아직 이에 대해 잘 모르지만, 이미지 로딩이 끝나기 전에 이미지가 차지하는 영역과 로딩 이후 차지하는 영역 사이의 차이로부터 발생하는 경우 같습니다.
더 많이 공부해야겠습니다.

그렇게 적용한 후 코드는 아래와 같습니다.

// page.tsx

// 배너 이미지
import Image from "next/image";

import image1 from "../assets/image-1.png"

...
  <div className="relative ...">
    <Image
       src={image1}
       alt="image1_alt"
       fill
	   loading="eager"
       className="z-[-1] object-cover ..."
    />
         
    ...
  </div>
...

Next/Image는 기본적으로 loading='lazy' 입니다.

// component.tsx

// 배너 이미지
import Image from "next/image";

import image2 from "../assets/image-2.png"

...
  <Image
	src={image2}
	alt="image2_alt"
 	placeholder="blur"
 	width={width}
 	height={height}
  />
...

적용결과

<Image /> 를 사용하여 새로고침시 (memory cache) 가 뜨는 것을, 즉 브라우저 캐싱이 되고 Etag 를 확인하는 네트워크 리퀘스트가 없는 것을 확인했다.

max-age 는 따로 설정하지 않았더니 31536000 초, 즉 1년(최댓값)으로 설정되어 있다.

이렇게 설정한 후 다시 Lighthouse 성능 측정을 해보았다.

LCP 시간이 크게 감소한 것을 알 수 있다.

결론

성능점수도 30점 정도 올려냈고,
또 새로고침 시 마다 발생하는 이미지 검증요청도 이제는 더 이상 발생하지 않는다.
또한, PNG로 불러오던 이미지 역시 WebP 형식으로 더 작은 사이즈로 가져옴을 확인했다.

Next에서 이미지를 적용할 때에는 Image 컴포넌트를 적극 활용해야 겠다. 지금은 local 이미지만 사용했지만, remote 이미지도 마찬가지로 적용할 수 있다.

또, Lighthouse의 성능 섹션별로 무엇을 의미하는 지를 정확히 알 필요가 있겠다.

참고

0개의 댓글