극한의 프론트엔드 성능최적화 2편 (Image 최적화)

Sming·2023년 5월 2일
102
post-thumbnail

이번에 알아 볼 최적화 과정은 image 최적화입니다. 🌄 🌄 🌄

1편에서 말했듯이 간단하게 next/image를 사용하지 않고 cdn과 html의 picture, source 태그와 함께 어떻게 최적화 했는지 알아볼 예정입니다.

cloudinary를 이용한 이미지 최적화

사실 next/image에서 이용하는 최적화를 cdn을 통해서 다 처리할 수 있습니다. 이미지 리사이징 ,포맷 변경, lqip이미지 생성, responsive 이미지 생성등 모든 것을 처리할 수 있습니다.

사실 clodinary 사용법이여서 이미지 최적화가 어떤 종류로 할 수 있는지만 보시면 좋을것같습니다.

이미지 리사이징

이미지 최적화의 가장 중요한 부분이라고 생각하는것이 이미지 리사이징입니당.

말그대로 기존 원본이미지를 렌더링할때의 이미지에 맞춰서 잘라서 받아오는 것입니다. 이미지 용량에서 가장 큰 비중을 차지 하는 것이 이미지의 크기이기 때문에 이 이미지의 사이즈를 줄이는 것이 가장 중요합니다.

https://<clodinary url>/w_600,h_400/...

이렇게 하면 width 600, height 400의 이미지가 생성됩니다.

포맷 최적화

이미지에는 다양한 포맷이 존재합니다. jpg, png, svg, webp, avif등이 있죠. 최적화되어있지 않은 대부분의 웹사이트는 png, jpg로 되어있을겁니다.

이 이미지 포맷이라는것도 이미지 압축률에 큰 영향을 주기에 차세대 이미지 형식인 webp, avif를 사용하는것이 좋습니다.

하지만 최신 기술이라고 하면 항상 가로막는 장벽이 존재하죠. 바로 브라우저 지원 여부 입니다. webp, avif가 아무리 좋아도 지원을 안하는 브라우저에서는 이용을 할 수 없죠.

그래서 cloudinary에서는 f_auto 옵션을 줄 시 브라우저에 알맞는 format으로 변경해서 줍니다. 최신버전의 chrome 같은 경우 avif 이런것들을 지원하지 않는 safari 브라우저같은 경우에는 jpeg 2로 이미지를 서빙하여 줍니다.

퀄리티 낮추기

다음은 이미지의 퀄리티를 낮추는 방법입니다. 이미지의 퀄리티를 줄이는 것도 이미지의 사이즈를 줄이는것입니다. 사실 이미지의 퀄리티를 낮춘다고 하면 굉장히 찝찝하지만 실제로 퀄리티를 75프로까지 줄여도 사람 눈에는 큰 차이를 느끼지 못한다고 합니다.

개인적으로 추천하는 퀄리티는 80 ~ 90 정도 입니다.

lqip생성

다음 최적화 방법은 lqip입니다. 앞의 내용에 비해서 비교적 생소한 방법일 것 같은데요. 바로 low quality image placeholder의 약자입니다.

image의 Skeleton을 단순히 회색으로 보여주는것이 아니라 그 이미지에 블러처리가 들어가고 퀄리티가 매우낮은 이미지를 만들어 내는겁니다.

평균적으로 10배이상 원본이미지 크기보다 줄어들며 원본 이미지를 보여주기 전에 이러한 low quality 이미지를 불러옴으로써 사용자가 빈 이미지를 보는것으로 방지할 수 있습니다.

캐시를 끄고 slow 3g 환경에서 테스트한 영상입니다.


이렇게 최적화한 전후 이미지를 비교해보면 408kb -> 14.4kb까지 줄어든것을 볼 수 있습니다. 여기에서 lqip까지 적용된다면 처음에 불러오는 이미지는 오직 700b정도 밖에 되지 않습니다.

react에서의 이미지 최적화

react에서의 이미지 최적화라고 적었지만 사실 모든 라이브러리에 해당하는 내용일겁니다. html, js만 사용한다면 구현할 수 있는것이기 때문이죠.

lazy loading

lazy loading은 이미지 자체를 최적화한다기 보다는 이미지를 불러오는 웹사이트를 최적화하는 기술입니다.

예를 들어 swiper를 통해서 carousel을 구현했을때 20개의 슬라이드에 있는 이미지를 모두 받아올 필요가 있을까요?
그렇게 된다면 웹사이트가 초기에 다운로드 받는 이미지가 많아지며 그만큼 블로킹이 발생할겁니다.

이것을 막기 위해 현재 보여지는것만 다운로드 받고 그 외의 것들을 나중에 다운로드받는 lazy loading이 필요합니다.


lazy loading을 구현하기 위해서 여러가지 방법을 이용할 수 있는데요.

  • lazySizes 같은 외부 라이브러리 이용
  • browser의 loading=lazy 옵션 이용
  • intersection observer를 이용한 구현

lazySizes같은 경우 모든 브라우저에서 지원하게 하며 반응형 이미지도 쉽게 lazy loading을 할 수 있도록 하는 라이브러리입니다.
https://github.com/aFarkas/lazysizes

browser의 loading='lazy' 옵션은 img 태그에 <img loading="lazy" /> 라고 작성을 하면 브라우저가 저절로 뷰포트에 도달할때 이미지를 다운로드 받습니다.
구현은 편하나 모든 브라우저의 지원을 하지 않기에 polyfill이용이 필요합니다.

intersection observer를 이용하는 방법은 data-src, src를 이용하여 초기에는 스켈레톤 이미지, 뷰포트에 도달할시 data-src에 있는 것을 src로 옮겨 화면에 보여주도록 하는데 체감상 loading='lazy' 보다 자연스럽지 못합니다.
역시 intersection observer를 지원하지 않는 경우가 있기 때문에 polyfill이 필요합니다.

주의할점

하지만 주의해야될 부분도 존재하는데요. 바로 초반에 보여야할 이미지에 lazy loading을 걸면 안된다는것입니다. 불필요하게 늦게 로딩이 될수 있기 때문입니다.

fetchPriority

lazy loading의 반대되는 개념입니다. 초반에 빨리 보여져야되는 이미지 태그에 사용시 다른 이미지들 보다 다운로드 받는 우선순위를 높여 보다 빠르게 화면에 그려지게 됩니다.

<img fetchPriority="high" />

간편하게 다음과 같이 이용하면 됩니다.

하지만 fetchPriority 같은 경우 typescript에 아직 존재하지 않기에

import { AriaAttributes, DOMAttributes } from 'react';

declare module 'react' {
  interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
    fetchPriority?: 'high' | 'low' | 'auto';
  }
}

이렇게 커스텀 d.ts 를 만들어서 typescript를 지원시켜야 합니다.

responsive image

다음은 responsive image입니다. 주로 웺사이트를 만들때 반응형으로 만들게 될텐데 이럴때 모바일에서도 데스크탑 이미지 정도의 크기를 이용한다면 불필요한 용량을 가지게 됩니다.

그래서 모바일에는 모바일 사이즈에 맞는 이미지, 데스크탑에서는 데스크탑 사이즈에 맞는 이미지를 보여줘야합니다.

이것은 html의 picture, source 태그를 통하여 구현할 수 있습니다.

  <picture>
    <source media="(max-width: 760px)" srcSet={mobileUrl} />
    <source media="(min-width: 761px)" srcSet={desktopUrl} />
  </picture>

이렇게 이용할 경우에 760px 이하일때는 mobileUrl을 보여주고 761px 이상일때는 desktopUrl을 보여주죠.


하지만 이렇게 picture tag와 source tag만을 이용하면 lazy loading이나 fetchPriority는 어떻게 쓰나라고 고민할 수도 있는데요. 그럴때는 밑의 코드 처럼 작성해주시면 됩니다.

  <picture>
    <source media="(max-width: 760px)" srcSet={mobileUrl} />
    <source media="(min-width: 761px)" srcSet={desktopUrl} />
  	<img alt="title" loading="lazy" />
  </picture>

lqip 이용하기

앞에서 lqip image를 만드는 방법을 봤었는데 이번에는 그 lqip 이미지를 실제로 어떻게 이용할지를 알아볼것입니다.

사실 간단하게 img태그를 감싸는 부모태그의 inline style의 background-image에 lqip이미지를 넣어주시면 됩니다.

	<div
      style={{
        marginRight: '20px',
        backgroundImage: `url(${blurImageUrl})`, // 여기!
        backgroundSize: `cover`,
        backgroundPosition: 'center',
        backgroundRpeat: 'no-repeat',
      }}
    >
    </div>
profile
딩구르르

4개의 댓글

comment-user-thumbnail
2023년 5월 5일

비용에 대한 얘기도 들어있으면 좋을 것 같아요

답글 달기
comment-user-thumbnail
2023년 5월 10일

Nice post. Keep it up. I found lot of useful and helpful information from your post.
Chipotle Feedback

1개의 답글
comment-user-thumbnail
2023년 12월 7일

Chipotle obtains information on its patrons using an online poll called "ChipotleFeedback." Customers' level of satisfaction with the restaurant overall is one of the survey's questions. https://chipotlefeedbacks.com/

답글 달기