[프로젝트] 성능 개선 (이미지 최적화, prefetch)

JUN·2024년 7월 17일

WDYTA

목록 보기
5/5
post-thumbnail

프로젝트를 마무리하고 성능 개선을 좀 더 하고싶어 리팩토링을 했다. 이미지 최적화를 통해 좀 더 로딩 속도를 빠르게 하기를 원했고, prefetch 적용 전과 후를 비교함으로써 어떠한 차이가 있는지 좀 더 딥하게 공부를 해보았다.

이미지 최적화

Next.js에서 Image 컴포넌트를 사용했기 때문에 기본적인 최적화 기능이 갖추어져있다. 예를들면 lazy loading, webp 형식으로의 변환, 캐싱 등이있다. 이미지를 요청하게 되면 최적화된 이미지가 cache/images 폴더에 생성이 된다.

또한 Image 태그에는 width와 height를 지정해야 next.js의 Image 컴포넌트를 잘 사용했다라고 할 수 있다. 근데 이미지 크기가 동적으로 변해야 한다면?

<div>
<Image
    src={src}
    alt={alt}
    fill
  />
</div>

width와 height 대신 fill 속성을 사용하면 된다. width와 height는 Image 컴포넌트를 감싸준 div태그에! 그러면 이미지가 부모 요소를 채우도록 동작하게 된다.

그래서 뭐를 개선했는데?

Image 컴포넌트를 사용해서 기본적인 최적화 기능은 갖추고 있었지만, 아쉬운 점들이 좀 보였다. 용량이 큰 사진일 수록 업로드 속도가 느리다는 것이었다. 당연한 얘기이지만 욕심이 생겨서 좀 더 시간을 단축시켜 보기로 했다.

1. 개선 전 측정한 시간(캐시 적용X, 3g slow 적용)

개선을 하기 전 시간을 보면 가장 큰 용량의 이미지는 227kB이며, 오래 걸린 이미지는 약 21.6s 소요된 것을 볼 수 있다. 아무리 3g 환경에서 측정했다하지만, 너무 오래걸린다는 느낌을 받았다.

2. size 속성 추가
첫 번째 개선 방법으로 Image 컴포넌트에 sizes 속성을 추가하는 것이었다. 이유는 Image 태그의 sizes속성 기본값이 100vw이다.
한 마디로 layout:fill을 주게 될 경우 무조건 뷰포트 크기에 따라 이미지를 요청하기 때문에 이를 방지하고자 sizes 속성을 지정해준 것.


const sizesConfig: Record<ImageProps['type'], string> = {
  product: '(max-width: 768px) 50vw, (max-width: 1024px) 50vw, 20vw',
  profile: '(max-width: 768px) 36px, (max-width: 1024px) 36px, 33vw',
  review: '(max-width: 768px) 60px, (max-width: 1024px) 80px, 33vw',
  detail: '(max-width: 768px) 335px, (max-width: 1024px) 280px, 355px',
};

 <Image
        draggable={false}
        src={src}
        fill
        alt={alt}
        sizes={sizesConfig[type]}
        priority
      />

sizes 속성만 추가했는데도, 이미지 크기 및 시간이 단축된 것을 볼 수 있었다.

3. imageSizes, deviceSizes
next.js에서 이미지는 따로 설정을 안 해주면 아래와 같은 기본값을 갖게 된다.

imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],

개발자 도구에서 이미지를 자세히 보면 기본값대로 모두 가져오게 되는 것이다. 불필요한 값들도 불러오니 좀 더 효율적으로 개선시켜보려고 했다.

imagesSizes는 Image컴포넌트에서 fill 속성을 사용하여 동적인 이미지 사이즈를 보여주고 싶을 때 사용된다. 앞서 fill 속성을 사용했기 때문에 imageSizes를 nextConfig에 추가했다.

    imageSizes: [32, 48, 128, 280, 384],
    deviceSizes: [640]

동작원리는 다음과 같다.

  product: '(max-width: 768px) 50vw, (max-width: 1024px) 50vw, 20vw',

브라우저 최대 너비가 768px일때 50vw로 지정을 했다. 이는 브라우저 너비의 1/2를 뜻한다. 즉, 브라우저 너비가 700px이라면 절반인 350px의 이미지 크기를 뜻하는데, 해당 값을 imageSizes와 비교해보자. 280과 385 사이이므로 350 이상의 값인 384의 값으로 이미지를 다운로드하게 된다.

prefetch

다음은 tanstack-query의 prefetch 기능이다. prefetch는 특정 페이지를 들어가기 전에 데이터들을 미리 가져와 캐싱하여 미리 로드한다. 그 결과 페이지 로딩이 빨라지게 된다.

 const queryClient = getQueryClient();
  await Promise.all([
        queryClient.prefetchInfiniteQuery(
      followerOptions(Number(userId), 'follower'),
    ),
    queryClient.prefetchInfiniteQuery(
      followeeOptions(Number(userId), 'followee'),
    ),
    queryClient.prefetchInfiniteQuery(
      productOptions(
        Number(userId),
        TAB_NAMES_ORIGIN.createdProduct,
        getUserCreatedProducts,
      ),
    ),
    queryClient.prefetchInfiniteQuery(
      productOptions(
        Number(userId),
        TAB_NAMES_ORIGIN.favoriteProduct,
        getUserFavoriteProducts,
      ),
    ),
    queryClient.prefetchInfiniteQuery(
      productOptions(
        Number(userId),
        TAB_NAMES_ORIGIN.reviewedProduct,
        getUserReviewedProducts,
      ),
    ),

프로필 페이지로 이동하면 리뷰 남긴 상품, 등록된 상품, 찜한 상품 이렇게 3개의 메뉴가 보이게 된다. 나는 이 세 메뉴에 대한 데이터들을 모두 미리 받아오고 싶었던 것이다. 그래서 prefetch를 적용했을 때와 안했을 때의 차이는 얼마나 날 지 분석을 해보았다.(3g slow 기준)

첫 번째 이미지는 prefetch 적용을 안했을 때이다. 네모 표시한 api 요청이 있는 것을 볼 수 있다. 이 부분에서 차이가 났다. 약 2초 정도이긴 하지만, 2초면 크게 느껴질 수 있는 시간이라고 생각한다. useQuery는 prefetch 중인 api를 중복 호출하지 않음!

추가적으로 프로필 내의 팔로워, 팔로잉 유저 리스트들도 prefetch를 사용하여 미리 데이터를 캐싱하고 싶었다. 위 방법과 동일하게 팔로워, 팔로잉 데이터들을 불러오는 동일한 함수와 쿼리키로 prefetch를 시도하였고, 그 결과 3g slow기준 약 2초 정도 로딩 속도를 단축할 수 있었다.


프로필 편집 모달

image.jpg1image.jpg2

프로필 편집을 하고 나면 느리게 업로드 됐던 이미지 이슈도 위에서 했던 이미지 최적화 방식으로 어느정도 해결이 되었다. 4~5MB인 이미지 기준으로 0.7s 정도 단축시켰다.

profile
웹 프론트엔드 취업 준비생입니다:)

0개의 댓글