이미지 최적화

ws·2024년 12월 4일

Coworkers

목록 보기
5/5

이미지 최적화의 중요성

웹 페이지에서 이미지는 가장 큰 용량을 차지하는 요소 중 하나입니다. 따라서, 이미지 크기를 최적화하면 로드 해야 할 리소스 용량을 절감하여 성능 개선이 가능합니다.

  • 아래는 Coworkers 팀 페이지의 네트워크 리소스를 분석한 결과로, 이미지 파일이 로드한 리소스 중 67% 의 용량을 차지했으며, 큰 용량으로 인해 로드 시간도 긴 것을 확인할 수 있습니다.
파일 유형총 크기 (KB)비율 (%)
이미지 (image)34367
자바스크립트 (script)11122
기타 (other)316
스타일시트 (stylesheet)235

  • 같은 방법으로 네이버 메인 페이지를 분석한 결과, 역시 이미지 리소스 크기의 비중이 54% 로 가장 컸습니다.
파일 유형총 크기 (KB)비율 (%)
image45054
script32038
stylesheet506
other152

이미지 최적화 방법

1. 차세대 이미지 형식 사용

차세대 이미지 형식인 WebPAVIF를 사용하면 이미지 품질을 유지하면서 파일 크기를 크게 줄일 수 있습니다.

  • WebP
    • 구글에서 개발한 이미지 형식으로, 손실 및 무손실 압축을 모두 지원하며 JPEG나 PNG보다 뛰어난 압축 효율을 제공합니다.
    • PNG 의 투명도, GIF 의 애니메이션을 지원합니다.
    • cwebp 커맨드라인 인코더를 사용하거나 Squoosh (GoogleChromeLabs 의 이미지 압축 웹앱) 를 통해 인코딩을 할 수 있습니다.
  • AVIF
    • AV1 비디오 코덱을 기반으로 한 이미지 형식으로, WebP보다도 더 높은 압축률과 품질을 제공합니다.

2. 적절한 크기 이미지 사용

이미지를 실제 표시될 크기에 맞게 조정하여 불필요한 데이터 전송을 방지합니다.

  • 이미지 리사이즈: 디스플레이 크기에 맞게 이미지를 조정하여 용량을 최소화합니다.
  • 반응형 이미지 사용: srcset과 sizes 속성을 활용하여 다양한 해상도와 기기에 맞는 이미지를 제공합니다.
  • 브라우저 최적화: 브라우저가 적절한 이미지를 선택할 수 있도록 설정하여 로딩 시간을 단축합니다.

3. CLS 개선

CLS(Cumulative Layout Shift)는 페이지의 시각적 안정성을 측정하는 지표로, 레이아웃 이동을 최소화하는 것이 중요합니다.

  • 고정 크기 지정: 이미지 태그에 width와 height 속성을 명시하여 공간을 미리 확보합니다.
  • CSS로 비율 유지: aspect-ratio 속성을 사용하여 이미지의 비율을 유지하면서 크기를 조정합니다.
  • 레이아웃 안정성 확보: 지연 로딩이나 동적 콘텐츠로 인해 레이아웃이 변하지 않도록 설계합니다.

4. 이미지 Lazy Loading

사용자가 실제로 이미지를 볼 때까지 로딩을 지연시켜 초기 로딩 속도를 향상시킵니다.

  • loading 속성 사용: < img > 태그에 loading="lazy"를 추가하여 브라우저 기본 지연 로딩을 활성화합니다.
  • JavaScript 라이브러리 활용: Intersection Observer API 등을 사용하여 커스텀 지연 로딩 기능을 구현합니다.
  • 필요한 경우 예외 처리: 중요한 이미지나 위fold 콘텐츠는 지연 로딩에서 제외합니다.

5. 이미지 Pre-loading

중요한 이미지를 미리 로드하여 사용자에게 빠른 콘텐츠 표시를 제공합니다.

  • **태그 사용**: 헤드 섹션에 추가하여 브라우저가 필요한 리소스를 미리 가져오도록 합니다.
  • 우선순위 설정: 가장 중요한 이미지에 대해 프리로딩을 적용하여 렌더링 차단을 방지합니다.
  • 리소스 효율성 고려: 프리로딩은 리소스 소비가 크므로 필요한 경우에만 사용합니다.

이미지 최적화 적용

1. 차세대 이미지 형식 사용

1-1. Next.js 에서 WebP 형식 사용

Coworkers 는 Next.js 프로젝트입니다.

Next.js 는 next/image 컴포넌트를 사용하여 자동으로 WebP 와 같은 차세대 이미지 형식을 지원하여 이미지 크기를 줄입니다.

next/image 컴포넌트는 config 에 formats 가 제공되지 않으면 image/webp 형식이 기본값이 됩니다. image/avif 를 formats 배열 앞 순서에 사용하면 AVIF 지원을 활성화할 수 있습니다.

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

위와 같이 next/image 컴포넌트를 사용한 경우, webp 형식으로 파일을 로드하는 것을 확인할 수 있습니다.


1-2. Next.js 에서 SVG 형식 사용

여기서 Member.svg 나 Thumbnail_team.svg 는 svg+xml 형식으로 로드해 상대적으로 크기가 크고 로드 시간이 오래 걸리는 것을 확인할 수 있습니다.

Next.js 는 기본적으로 SVG 이미지를 최적화하지 않습니다.

기본 로더는 몇 가지 이유로 SVG 이미지를 최적화하지 않습니다.
첫째, SVG는 크기를 조정해도 손실이 없는 벡터 형식입니다.
둘째, SVG는 HTML/CSS와 동일한 기능을 많이 가지므로 적절한 콘텐츠 보안 정책(CSP) 헤더 없이도 취약성을 초래할 수 있습니다.
https://nextjs-ko.org/docs/pages/api-reference/components/image#dangerouslyallowsvg

SVG 를 사용하는 이유
SVG 는 2차원 벡터 그래픽을 서술하는 XML 기반의 마크업 언어입니다.
해상도에 관계 없이 모든 사이즈에서 선명하게 렌더링되는 이미지를 제공합니다.
다양한 디바이스와 화면 크기에서 이미지 품질을 유지하기 위해 사용합니다.
SVG 는 XML 기반이기 때문에 CSS, JavaScript 를 사용하여 이미지 스타일 변경이나 애니메이션 적용을 할 수 있습니다.
SVG 를 React 컴포넌트로 import 해서 재사용성을 높이고 모듈화를 할 수 있습니다.
Next.js 는 React 기반이지만 React 에서 사용하는 방식으로 svg 를 읽으면 읽을 수 없다는 에러가 뜹니다. svgr 을 사용하여 컴포넌트 형식으로 사용할 수 있습니다.

SVGR 사용
SVGR 은 Next.js 를 비롯해 Remix, Node.js 등에서 SVG 를 React 컴포넌트로 변환해줍니다.
React 컴포넌트로 만들면 사용하기 편리해지고 재사용성을 높일 수 있습니다.
변환 과정에는 아래와 같은 것들이 있습니다.
1. SVGO 라는 SVG 최적화 도구, Node.js 라이브러리를 사용하여 최적화
2. 여러 단계로 HTML 을 JSX 로 변환
3. React 컴포넌트에 JSX 래핑
4. Babel AST 를 코드로 변환
5. Prettier 를 사용하여 코드 포맷팅
아래 링크에서 Next.js 에서 svgr 을 사용하는 방법을 확인할 수 있습니다.
https://react-svgr.com/docs/next/

위 네트워크 리소스 파일은 Thumbname_team.svg 파일을 React 컴포넌트로 변환하여 사용하는 SVG 파일 모듈입니다. next/image 사용에서 svgr 를 활용한 방법으로 수정한 결과 파일 크기는 훨씬 커지고(1.5kB → 258kB) 그에 따라 로드 시간이 늘어난 것(3밀리초 → 34 밀리초)을 확인했습니다.

종합하면, SVG 파일은 next/image 를 사용하는 것이 최적화가 가장 잘 되며, 스크립트를 이용한 스타일 변경이나 재사용성을 높이기 위해 svgr 을 사용해야 한다면 필수적인 이미지만 svg 로 사용해야 효율적일 것입니다.

Thumbail_team 파일은 svg 로 활용해야 할 이유가 없기 때문에 webp 로 변환하여 사용하는 것이 가장 좋은 방법입니다.

Sqoosh(GoogleChromeLabs 의 이미지 압축 웹앱) 를 통해 Thumbail_team.svg 를 webp 로 변환한 결과 품질을 100으로 설정해도 크기가 61%(3.9kB → 1.5kB) 줄어든 것을 확인했습니다.

물론 앞에서 설명했듯이 Squoosh 나 cwebp 를 사용하지 않고, 다른 형식을 사용해도 Next.js 에서 next/image 컴포넌트를 사용하면 자동으로 WebP 형식을 지원합니다.

svg 에서 webp 로 변환된 결과 기존 1.5kB 에서 1.0kB 로 크기가 줄어든 것을 확인했습니다.

팀 페이지 내 모든 불필요한 svg 파일에 적용한 결과 FCP(First Contentful Paint) 55% 단축(0.9초 → 0.5초), LCP(Largest Contentful Paint) 45% 단축(1.3초 → 0.6초)하는 결과를 얻을 수 있었습니다.

FCP(First Contentful Paint)
페이지 로드가 시작된 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링되는 시점까지의 시간을 측정합니다.

LCP(Largest Contentful Paint)
페이지의 주요 콘텐츠가 로드되었을 가능성이 있는 페이지 로드 타임라인의 지점을 표시합니다.


2. 적절한 크기 이미지 사용

적절한 크기의 이미지를 사용한다는 것은 이미지를 실제 표시될 크기에 맞게 조정하여 불필요한 데이터 전송을 방지한다는 것을 의미합니다.

반응형 이미지를 처리하기 위해서 뷰포트의 크기부터 사용자 화면의 해상도 등 많은 환경을 고려해야 합니다.

하지만 HTML img 의 srcsetsizes 를 통해 쉽게는 이미지의 크기를 설정하는 것만으로 대부분의 고려 사항을 사용자 브라우저에 맡길 수 있습니다.

srcset
srcset 은 브라우저에서 선택할 수 있는 이미지 세트와 각 이미지 크기를 정의합니다.

sizes
sizes 는 일련의 미디어 조건(ex: 화면 너비)을 정의하고 특정 미디어 조건에 해당할 때 어떤 이미지 크기를 선택하는 것이 가장 좋을지 알려줍니다.

브라우저 동작
1. 기기 너비를 확인합니다.
2. sizes 목록에서 어떤 미디어 조건이 가장 먼저 참인지 알아냅니다.
3. 해당 미디어 쿼리에 지정된 슬롯 크기를 확인합니다.
4. 슬롯과 크기가 같은 srcset 목록에 참조된 이미지 또는 이미지가 없는 경우 선택한 슬롯 크기보다 큰 첫 번째 이미지를 로드합니다.
https://developer.mozilla.org/ko/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images

Next.js 의 next/image 컴포넌트도 sizes 속성을 제공하는데, 성능과 관련된 두 가지 동작을 수행합니다.

  1. sizes 값을 통해 next/image 에서 자동 생성한 srcset 에서 다운로드할 이미지 크기를 브라우저가 결정합니다.
  2. sizes 값이 없으면 고정 크기 이미지( 1x / 2x / 등 ) 에 적합한 srcset 이 생성되고, 정의된 경우엔 반응형 이미지(640w/750w/등) 에 적합한 srcset 이 생성됩니다.

Next.js 에서 제공하는 next/Image 컴포넌트를 활용하여 반응형 이미지를 만드는 방법은 다음과 같습니다.

  1. 정적 임포트를 사용하는 반응형 이미지
    1. 소스 이미지가 동적이지 않으면, 정적 임포트를 사용해 반응형 이미지를 생성할 수 있습니다.
  2. 종횡비를 사용하는 반응형 이미지
    1. 소스 이미지가 동적이거나 원격 URL 인 경우, 반응형 이미지의 올바른 종횡비를 설정하기 위해 widthheigth 를 제공해야 합니다.
  3. fill 을 사용하는 반응형 이미지
    1. 종횡비를 모르는 경우, fill 속성을 설정하고 부모 요소에 position: relative 를 설정해야 합니다.
    2. 원하는 스트레치 또는 자르기 동작에 따라 object-fit 스타일을 선택적으로 설정할 수 있습니다.

Coworkers 프로젝트에서는 반응형 이미지에 대해 이미 위와 같은 처리를 해 주었기 때문에 추가적인 수정은 하지 않았습니다.


3. CLS 개선

CLS (누적 레이아웃 변경) 는 사용자가 예상치 못한 레이아웃 이동을 경험하는 빈도를 수량화하여 시각적인 안정성을 나타내는 지표입니다.

이미지에 width 와 heigth 를 명시하지 않으면 reflow 현상이 발생하여 CLS 가 발생합니다.

Next.js 의 next/image 를 사용하면 필수적으로 width 와 heigth 속성을 입력해야 하고, 이 속성을 결합하여 이미지가 로드되기 전 브라우저가 이미지 공간을 예약하도록 비율을 결정하는 역할을 합니다. (너비와 높이를 알 수 없는 경우 fill 속성 사용)

Coworkers 프로젝트의 팀페이지도 Lighthouse 를 통해 검사한 결과 CLS 값이 0.02 로 양호한 수치를 나타내고 있음을 확인했습니다.


4. 이미지 Lazy Loading

Lazy loading 은 사용자가 웹페이지를 로드할 때, 보이지 않는 이미지들을 불필요하게 미리 로드하여 네트워크 대역폭을 낭비하지 않기 위해 사용하는 기술입니다.

예를 들어, 게시판 페이지 첫 화면에서 사용자는 3 개의 게시물만 확인할 수 있지만 50 개의 모든 게시물에 대한 이미지가 로드되면 대역폭을 비효율적으로 사용하게 됩니다.

lazy loading 을 적용하는 방법은 < img >, next/imageloading 속성을 이용하는 방법이 있습니다.

위 방법은 브라우저에서 lazy loading 을 지원하는 방법으로, 외부 라이브러리가 필요하지 않습니다.

❗페이지가 로드될 때 표시 영역에 있을 가능성이 있는 이미지, 특히 LCP 이미지는 lazy loading 을 적용하지 않아야 합니다.

loading 속성 사용

Next.js 의 next/image 컴포넌트의 loading 속성을 사용할 수 있습니다. (HTML < img > 태그의 loading 속성과 같습니다.)

loading 속성은 이미지의 로딩 동작을 나타내며, 기본값은 lazy 입니다. *next/image 가 아닌 < img > 에서는 eager 값이 기본값입니다.*

  • lazy 인 경우, 이미지는 뷰포트에서 계산된 거리에 도달할 때까지 로딩이 연기됩니다.
  • eager 인 경우, 이미지는 즉시 로드됩니다.

Coworkers 에서는 next/image 컴포넌트를 사용하고 있기 때문에 기본값으로 lazy loading 이 적용되어 있으며, 이미지가 포함되는 목록의 경우 무한 로딩이나 페이지네이션이 적용되어 있기 때문에 추가적으로 수정해야 할 사항은 없습니다.

5. 이미지 Pre-loading

이미지 pre-loading 은 사용자가 이미지를 클릭하기 전에 미리 로드하여 로딩 속도를 개선하는 기법입니다.

고해상도의 이미지를 로드하면 발생할 수 있는 버벅임을 해결하기 위해 특정 이벤트가 발생할 때 이미지를 미리 로드하는 방법을 적용할 수 있습니다.

적용 예시 (이벤트 기반 pre-loading)

  • 사용자가 특정 버튼이나 링크에 마우스를 올릴 때(onMouseEnter), 이미지를 미리 로드합니다.
import { useEffect } from 'react';

function preloadImage(url) {
  const img = new Image();
  img.src = url;
}

export default function PreloadExample() {
  const handleMouseEnter = () => {
    preloadImage('/images/high-res-image.webp');
  };

  return (
    <button onMouseEnter={handleMouseEnter}>
      고해상도 이미지 보기
    </button>
  );
}

적용 예시 (Next.js 의 priority 속성 활용)

  • Next.js 의 next/image priority 속성을 통해 중요한 이미지에 대해 자동으로 pre-loading 을 적용할 수 있습니다.
import Image from 'next/image';

export default function TeamProfile({ group }) {
  return (
    <Image
      src={group.image}
      alt="team-profile"
      width={100}
      height={100}
      priority
    />
  );
}

Next.js 에서 Image 컴포넌트 속성의 priority 를 true 로 설정하면, preload 됩니다. 그리고 priority 를 사용하는 이미지는 자동으로 지연 로딩이 비활성화됩니다. 또한, LCP 요소로 감지된 이미지에 priority 를 사용하면, 유의미한 개선을 할 수 있다고 설명하고 있습니다. (https://nextjs.org/docs/app/building-your-application/optimizing/images)

<Image
  src="/images/Thumbnail_team.webp"
  alt="thumbnail"
  width={181}
  height={64}
  priority
/>

LCP 로 감지된 이미지에 위와 같이 priority 속성을 추가한 결과, 아래 lighthouse 분석 결과에서 볼 수 있듯이 priority 가 적용된 경우 약 30ms 빠르게 로드되었음을 확인했습니다.

이렇게 Next.js 에서는 next/image 컴포넌트의 priority 속성을 지정하면 Next.js 가 자동으로 HTML 을 렌더링할 때, HTML 의 태그 내에 다음과 같이 프리로드 태그를 추가합니다. 이렇게 하면 브라우저가 HTML 파싱 시점부터 즉시 이미지를 로드하기 시작해서 사용자에게 더 빠르게 보여줄 수 있습니다.


결론

이미지 최적화는 웹 페이지의 성능 개선에 핵심적인 역할을 합니다. 웹 페이지에서 이미지가 차지하는 비중이 매우 크기 때문에, 이미지 용량을 줄이면 로딩 속도 개선과 사용자 경험 향상에 크게 기여할 수 있습니다.

  • 차세대 이미지 형식: WebP와 AVIF 같은 최신 이미지 형식을 사용하면, 기존 JPEG나 PNG보다 훨씬 효율적인 압축이 가능해서 품질을 유지하면서 파일 크기를 줄일 수 있습니다.
  • 적절한 크기 이미지 사용: 실제 표시될 크기에 맞게 이미지를 조정하고, 반응형 이미지를 적용함으로써 불필요한 데이터 전송을 최소화할 수 있습니다.
  • CLS 개선: 이미지에 명시적인 width와 height 또는 aspect-ratio를 설정하여 레이아웃 이동을 방지하면, 시각적 안정성을 확보할 수 있습니다.
  • Lazy Loading 및 Pre-loading: 보이지 않는 이미지는 지연 로딩하고, 중요한 LCP 이미지에 priority 속성을 적용하여 미리 로드하면, 초기 렌더링 속도와 전체적인 성능 지표를 개선할 수 있습니다.

이와 같이, 다양한 이미지 최적화 전략을 활용하면 웹사이트의 로딩 시간을 단축하고, 사용자에게 빠르고 쾌적한 사용 경험을 제공할 수 있습니다.


참고

https://web.dev/articles/browser-level-image-lazy-loading?hl=ko

https://web.dev/learn/images/webp?hl=ko

https://web.dev/learn/design/responsive-images?hl=ko#sizes

https://developer.mozilla.org/ko/docs/Web/HTML/Element/img

https://nextjs.org/docs/pages/building-your-application/optimizing/images

0개의 댓글