주요 성능 문제

이미지는 총 전송 크기 및 페이지당 요청 수 둘 다 면에서 가장 큰 웹 애셋입니다. 웹페이지의 총 전송 크기 중앙값은 2022년 6월 기준 약 2MB로, 이미지만 약 절반을 차지합니다. 이미지 요청을 최적화하는 것이 할 수 있는 가장 큰 성능 최적화라고 해도 과장은 아닙니다.

나중에는 모든 결과물에 대해 하나의 이미지를 제공하려고 함으로써 발생하는 문제를 해결하기 위해 반응형 이미지가 어떻게 발전해 왔는지 알아보겠습니다. 이 섹션에서는 이미지와 관련된 주요 실적 측정항목과 이미지를 개선하는 방법을 설명합니다.

이미지 요청 연기

이미지 요청을 가능한 한 작고 효율적으로 만드는 여러 가지 방법을 알아보려고 하지만, 항상 가장 빠른 이미지 요청은 실행되지 않는 요청일 것입니다. 따라서 이미지 확장 소재를 사용자에게 제공하는 방식에 적용할 수 있는 가장 효과적인 변화인 loading="lazy" 속성을 바로 알려드리고자 합니다.

<img src="image.jpg" loading="lazy" alt="…">

이 속성은 이미지가 사용자의 표시 영역에 가까워질 때까지 이미지를 요청하지 않도록 하여 초기 페이지 로드(브라우저가 가장 붐비는 시간)를 지연시키고 주요 렌더링 경로에서 이러한 요청을 삭제합니다.

실제로 이 속성을 사용하면 매우 간단하므로 성능에 크게 긍정적인 영향을 미칠 수 있습니다. 사용자의 표시 영역 내에 포함되지 않는 이미지는 요청되지 않으며 사용자가 절대 볼 수 없는 이미지에 대역폭을 낭비하지 않습니다.

하지만 한 가지 주의할 점이 있습니다. 이러한 요청을 지연시키면 최대한 빨리 이미지를 요청하기 위해 브라우저의 고도로 최적화된 프로세스를 활용하지 않는다는 의미입니다. loading="lazy"가 레이아웃 상단 img 요소에 사용되어 페이지가 처음 로드될 때 사용자의 표시 영역에 포함될 가능성이 더 크다면 최종 사용자가 이러한 이미지를 상당히 느리게 느낄 수 있습니다.

우선순위 가져오기

loading 속성은 개발자가 웹브라우저에서 요청의 우선순위를 정하는 방식을 더욱 세밀하게 제어할 수 있도록 하기 위한 대규모 웹 표준 노력의 예입니다.

브라우저의 우선순위를 가져오기 위한 기본 접근 방식을 알고 있을 가능성이 높습니다. 예를 들어 문서의 <head>에 있는 외부 CSS 파일 요청은 렌더링을 차단하기에 충분히 필수적인 것으로 간주되는 반면 </body> 바로 위에 있는 외부 JavaScript 파일에 관한 요청은 렌더링이 완료될 때까지 지연됩니다. <img>loading 속성 값이 '지연'인 경우 브라우저에서 사용자에게 표시할 것을 결정할 때까지 연결된 이미지 요청이 지연됩니다. 그렇지 않으면 이 이미지의 우선순위가 페이지의 다른 이미지와 동일합니다.

fetchpriority 속성은 개발자에게 애셋 우선순위를 더 세밀하게 제어하여 동일한 유형의 리소스에 비해 리소스를 '높음' 및 '낮음' 우선순위로 표시할 수 있도록 하는 데 목적이 있습니다. fetchpriority의 사용 사례는 loading 속성과 유사하지만 훨씬 더 광범위합니다. 예를 들어 사용자 상호작용(이미지가 사용자의 표시 영역에 속하는지 여부)에만 표시된 이미지에 fetchpriority="low"를 사용하여 페이지의 다른 위치에 표시되는 이미지의 우선순위를 지정하거나 fetchpriority="high"를 사용하여 페이지가 렌더링되는 즉시 표시 영역에 즉시 표시되는 이미지에 우선순위를 지정할 수 있습니다.

fetchpriority는 브라우저 동작을 근본적으로 변경하지 않는다는 점에서 loading와 다릅니다. 브라우저가 특정 애셋을 다른 애셋보다 먼저 로드하도록 지시하지 않고, 대신 애셋 요청과 관련된 결정을 위한 중요한 컨텍스트를 제공합니다.

이미지의 영향 측정

이미지 애셋을 최적화할 때 인지된 성능이 총 전송 크기만 사용하는 것보다 더 중요하고 측정하기가 더 어려운 경우가 많습니다.

웹 바이탈은 웹 서버의 느린 응답 시간, 렌더링 문제, 상호작용 지연과 같은 문제를 강조하여 웹 사용자의 웹 경험을 개선하기 위한 측정 가능하고 실행 가능한 측정항목과 안내를 제공합니다. 코어 웹 바이탈은 이러한 목표의 하위 집합으로, 사용자의 직접적인 개별 페이지 경험에 초점을 맞춘 것으로, 이러한 측정 세트를 종합하여 사용자에게 환경이 얼마나 빠르게 느껴지는지 결정합니다.

누적 레이아웃 변경

레이아웃 변경 횟수 (CLS)는 시각적 안정성을 측정합니다. 이는 애셋이 로드되고 페이지가 렌더링될 때 페이지의 콘텐츠 레이아웃이 변경되는 정도를 캡처하는 측정항목입니다. 지연된 웹폰트 또는 이미지 소스가 갑자기 렌더링되거나 상호작용 요소가 갑자기 포인터에서 멀어짐에 따라 웹을 사용하는 데 상당한 시간을 소비한 사용자는 페이지가 '점프'되어 오랫동안 텍스트에서 위치를 잃었습니다. CLS가 높으면 성가시고 최악의 경우 사용자 오류의 원인이 됩니다. 예를 들어 사용자가 클릭하는 것과 마찬가지로 '취소' 버튼이 이전에 '확인' 버튼이 있던 공간으로 이동합니다.

로드 시간이 길고 레이아웃에서 차지할 수 있는 공간이 많기 때문에 이미지가 CLS 점수 상승의 일반적인 원인으로 작용합니다.

최신 브라우저의 최근 변화 덕분에 이미지로 인해 높은 CLS 점수를 피하기가 생각보다 쉬워졌습니다.

몇 년 동안 프런트엔드에서 일해 왔다면 <img>widthheight 속성에 익숙할 것입니다. CSS가 보편화되기 전에는 이러한 속성이 이미지 크기를 제어하는 유일한 방법이었습니다.

<img src="image.jpg" height="200" width="400" alt="…">

이러한 속성은 스타일 지정 문제를 마크업과 별도로 유지하기 위해 더 이상 사용되지 않습니다. 특히 반응형 웹 디자인에서 CSS를 통해 비율 기반 크기를 지정해야 했기 때문입니다. 반응형 웹 디자인 초기에는 '사용하지 않는 widthheight 속성 삭제'가 일반적인 조언이었습니다. CSS에서 지정한 값(max-width: 100%height: auto)이 이를 재정의했기 때문입니다.

<img src="image.jpg" alt="…">
img {
  max-width: 100%;
  height: auto;
}

이전 예와 같이 heightwidth 속성을 삭제했지만 이 상황에서 브라우저가 이미지 높이를 결정하기 위해 사용할 수 있는 유일한 방법은 스타일시트가 적용된 후 레이아웃에서 차지하는 공간의 너비에 따라 소스를 요청하고 파싱한 다음 고유한 가로세로 비율로 렌더링하는 것입니다. 이 프로세스의 대부분은 페이지가 렌더링된 후에 발생하며 새로 계산된 높이로 인해 추가 레이아웃 변경이 발생합니다.

2019년부터 widthheight 속성을 다르게 처리하도록 브라우저 동작이 업데이트되었습니다. 이러한 속성 값을 사용하여 레이아웃에 있는 img 요소의 고정된 픽셀 기반 크기를 결정하는 대신 이러한 속성은 구문이 동일하지만 이미지의 가로세로 비율을 나타내는 것으로 생각할 수 있습니다. 최신 브라우저는 페이지가 렌더링되기 전에 img 요소의 고유한 가로세로 비율을 결정하기 위해 이러한 값을 서로 비교하여 나눕니다. 따라서 레이아웃이 렌더링될 때 이미지가 차지할 공간을 예약할 수 있습니다.

규칙에 따라 max-width: 100%와 함께 height: auto를 지정하여 HTML 속성의 높이를 재정의했는지 확인하는 한, 항상 이미지 소스의 고유 크기와 일치하는 값과 함께 <img>heightwidth 속성을 사용해야 합니다.

<img src="image.jpg" height="200" width="400" alt="…">
img {
  max-width: 100%;
  height: auto;
}

<img> 요소에 widthheight 속성을 사용하면 이미지로 인해 높은 CLS 점수를 피할 수 있습니다.

이 접근 방식은 오랫동안 확립된 브라우저 동작을 기반으로 하므로 단점이 없습니다. 기본 CSS를 지원하는 모든 브라우저는 마크업의 heightwidth 속성이 스타일로 재정의되어 항상 기존 방식대로 작동합니다.

widthheight 속성은 이미지에 필요한 레이아웃 공간을 예약하여 CLS 문제를 완벽하게 회피하지만, 사용자가 이미지가 전송되고 렌더링될 때까지 기다리는 동안 빈 간격이나 품질이 낮은 자리표시자를 제공하는 것도 이상적이지 않습니다. 느린 로드 이미지의 측정 가능하고 인지 가능한 영향을 완화할 수 있는 방법이 있지만, 완전히 렌더링된 이미지를 사용자에게 더 빠르게 표시하는 유일한 방법은 전송 크기를 줄이는 것입니다.

콘텐츠가 포함된 최대 페인트

최대 콘텐츠 렌더링 시간 (LCP)은 사용자의 표시 영역에 표시되는 가장 큰 '콘텐츠가 포함된' 요소, 즉 표시되는 페이지에서 가장 많은 비율을 차지하는 콘텐츠 요소를 렌더링하는 데 걸리는 시간을 측정합니다. 표면적으로는 지나치게 구체적인 측정항목처럼 보일 수 있지만, 이 요소는 사용자의 관점에서 페이지 대부분이 렌더링된 지점을 나타내는 실용적인 프록시 역할을 합니다. LCP는 인지되는 성능의 중요한 측정 요소입니다.

DOMContentLoaded 또는 window.onload 이벤트와 같은 측정항목은 현재 페이지 로드 프로세스가 기술적으로 완료된 시점을 확인하는 데 유용할 수 있지만, 페이지의 사용자 환경과 반드시 일치하지는 않습니다. 사용자의 표시 영역 밖의 요소를 렌더링할 때 약간의 지연이 발생하면 이러한 측정항목 중 하나가 고려되지만 실제 사용자는 전혀 감지하지 못할 가능성이 높습니다. LCP가 길면 사용자의 페이지 첫인상(현재 표시 영역 내에서 가장 중요한 콘텐츠)이 페이지가 느리거나 완전히 깨진다는 의미입니다.

LCP에서 포착하는 사용자 인식은 사용자 경험에 직접적인 영향을 미칩니다. 작년에 실시한 Vodafone에서 진행한 실험에 따르면 LCP가 31% 개선되었을 때 총 사용자 수가 8% 증가했을 뿐만 아니라 총 사용자 수에서 15% 상승한 것으로 나타났습니다. 즉, 잠재고객이 된 방문자 수 ('방문자 대비 리드 비율')도 15%, 장바구니를 방문한 사용자 수 ('장바구니 방문 사용자 수'도 11% 개선됨)가 나타났습니다.

웹페이지의 70% 이상에서 초기 표시 영역에서 가장 큰 요소에는 독립형 <img> 요소 또는 배경 이미지가 있는 요소로 이미지가 포함됩니다. 즉, 페이지 LCP 점수의 70% 는 이미지 성능을 기반으로 합니다. 그 이유를 파악하는 것은 그리 상상력을 하지 않아도 됩니다. 시선을 사로잡는 큰 이미지와 로고는 '스크롤 없이 볼 수 있는 부분'에 배치될 가능성이 높기 때문입니다.

web.dev 페이지의 콘솔에 강조표시된 LCP

LCP 지연을 방지하기 위해 취할 수 있는 몇 가지 단계가 있습니다. 첫째, '스크롤 없이 볼 수 있는 부분' 이미지에 loading="lazy"를 지정하지 마세요. 페이지가 렌더링될 때까지 요청을 지연하면 LCP 점수에 막대한 부정적인 영향을 미칠 수 있기 때문입니다. 둘째, fetchpriority="high"를 사용하면 이 이미지를 전송할 때 페이지의 다른 위치에 있는 이미지보다 우선순위가 높아야 한다는 것을 브라우저에 알릴 수 있습니다.

이러한 규칙을 염두에 두고 페이지의 LCP 점수를 개선하기 위해 할 수 있는 가장 중요한 작업은 이미지를 전송하고 렌더링하는 데 걸리는 시간을 줄이는 것입니다. 이렇게 하려면 품질 저하 없이 이미지 소스를 최대한 작고 효율적으로 유지하고 사용자가 탐색 컨텍스트에 가장 적합한 이미지 애셋만 받도록 해야 합니다.

결론

이미지 애셋은 페이지를 렌더링하는 데 필요한 다른 모든 애셋을 전송하는 데 소비되는 대역폭을 제외하고 사용자의 대역폭을 가장 많이 소모하는 요소입니다. 이미지는 주변 페이지 레이아웃이 렌더링된 도중과 이후에 모두 인지된 성능 면에서 심각한 문제를 일으킵니다. 간단히 말해, 이미지 확장 소재는 손상을 일으킵니다.

'이미지가 적으면 웹이 더 나아질 것 같다'는 말은 성능 면에서 확신이 들지만 사용자에게 엄청난 불편을 초래할 수 있습니다. 이미지는 웹의 중요한 부분이므로 성능만을 위해 의미 있는 콘텐츠의 품질을 타협해서는 안 됩니다.

이 과정의 나머지 부분에서는 이미지 애셋의 기반이 되는 기술과 품질 저하 없이 성능 영향을 완화하는 기술에 관해 알아봅니다.