Lighthouse로 블로그 성능 약 2배 개선하기

coggiee
12 min readJul 1, 2024

이번 포스팅에서는 lighthouse 이용하여 블로그의 성능을 개선해보려고 합니다. 먼저 성능 개선 전/후 점수를 살펴보겠습니다.

개선 전 모바일 측정 결과
개선 후 데스크탑 측정 결과

개선 전은 데스크탑 측정 사진이 없어서, 모바일 측정 사진으로 대체 했습니다. 개선 전 데스크탑 점수는 사진과 다르게 45점을 받았습니다. 이러나 저러나 여전히 빨간 불은 동일하네요.

가볍게 읽어보고 들어가기

Lighthouse의 정확한 점수를 얻기 위해서는 development가 아닌 production에서 측정해야 합니다.

저는 처음에 성능 측정을 development환경에서 수행했습니다. 그랬더니 기가 막히게 낮은 점수가 나와 당황을 했었던 기억이 있네요. 혹시나 해서 배포 환경에서 측정을 해봤더니, 점수가 더 높게 측정이 됐습니다.

왜 그럴까요? 이러한 차이는 두 환경에서의 빌드 프로세스와 최적화 수준이 다르기 때문입니다.

더 자세히 한 번 살펴볼까요?

개발 환경

  • 소스 맵 및 디버깅 정보 포함: 개발 환경에서 빌드된 애플리케이션은 디버깅을 용이하게 하기 위해 소스 맵과 추가적인 디버깅 정보를 포함합니다. 이러한 추가 데이터는 파일 크기를 증가시키며, 로딩 시간에 영향을 미칩니다.
  • 최적화되지 않은 자원: 개발 모드에서는 퍼포먼스 최적화보다는 빠른 빌드 시간과 개발 편의성에 중점을 둡니다. 따라서, 이미지 최적화, 코드 스플리팅, 미사용 코드 제거(트리 쉐이킹) 등의 프로덕션 빌드에서 적용되는 최적화 기법이 적용되지 않습니다.
  • Hot Module Replacement (HMR): 개발 중에는 HMR이 활성화되어 있어, 코드 변경 시 페이지 전체를 리로드하지 않고도 변경사항을 반영할 수 있습니다. 개발 편의성을 위한 이 기능은, 추가적인 자바스크립트 코드가 포함되어 성능에 영향을 줄 수 있습니다.

프로덕션 환경

  • 코드 최적화: 프로덕션 빌드 과정에서는 코드가 압축되고, 미사용 코드가 제거되며, 자동으로 코드 스플리팅이 적용됩니다. 이러한 최적화는 애플리케이션의 로딩 시간을 단축시키고, 전반적인 성능을 향상시킵니다.
  • 에셋 최적화: 이미지와 같은 정적 에셋도 프로덕션 빌드 과정에서 최적화됩니다. 예를 들어, 이미지 크기가 자동으로 조정되거나, 최적의 포맷으로 변환됩니다.
  • 서버 사이드 렌더링(SSR) 및 정적 사이트 생성(SSG): Next.js는 프로덕션 빌드에서 SSR과 SSG를 통해 페이지를 사전에 렌더링할 수 있습니다. 이는 첫 페이지 로딩 시간을 크게 단축시키며, 이로 인해 Lighthouse 점수가 향상됩니다.
  • 캐싱 전략: 배포 환경에서는 웹 자원들에 캐싱 전략을 적용하여 반복적인 요청에 대한 로딩 속도를 줄일 수 있습니다.

결론적으로, 개발 환경에서 낮은 Lighthouse 점수는 DX 경험과 반비례 관계 때문에 발생한다고 볼 수 있습니다. 반면에 프로덕션 환경에서는 사용자 경험을 최우선으로 고려하여 모든 콘텐츠와 코드가 최적화되므로, 월등히 좋은 성능을 보여준다고 볼 수 있겠네요.

어떤 항목을 개선했나요?

위에서 보았듯이, Performance(성능) 점수가 처참했기 때문에 최우선적으로 개선을 했고, 추가적으로는 Accessbility(접근성) 을 개선했습니다.

Performance를 개선해보자

LCP(Largest Contentful Paint): 사용자가 처음 페이지로 이동한 시점을 기준으로 표시 영역에 표시되는 가장 큰 이미지 또는 텍스트 블록의 렌더링 시간을 의미합니다. 좋은 LCP 점수는 2.5초 이하여야 합니다.

낮은 Performance 점수의 대부분은 LCP가 차지하는 것으로 보였습니다. 거의 62.9초가 걸렸네요. LCP에 가장 큰 영향을 끼치는 요소는 이미지입니다. 저 또한 포스트에 사용되는 이미지가 원인이었습니다.

그렇기 때문에, 가성비 좋은 이미지 최적화만 충분히 해준다면 Performance 점수가 월등히 높아집니다.

이미지를 최적화해보자

어떻게 하면 이미지를 최적화할 수 있을까요? NextJS에서는 기본적으로 <Image> 컴포넌트를 제공하며, 아래의 기능을 제공합니다.

  • WebP, AVIF 같은 최신 이미지 포맷을 사용하여 각 디바이스에 맞게 이미지의 사이즈를 자동으로 조절
  • lazy-loading을 기본으로 사용
  • Image Resizing

<Image>컴포넌트는 priority 속성을 제공합니다. priority 속성은 해당 이미지에 높은 우선순위를 부여하고 preload 합니다. (단, priority를 사용하면 해당 이미지는 지연 로딩(lazy-loading)이 적용되지 않습니다.)

NextJS의 이미지 최적화 원리가 궁금하시다면, 아래를 참고해주세요. NextJS는 어떻게 이미지를 최적화할까? NextJS Image Component

Large Layout Shift를 개선해보자

Layout Shift란, 늦게 로드되는 컨텐츠들로 인해, 먼저 로딩된 컨텐츠가 밀려나는 현상(레이아웃이 밀려나는 현상)을 의미합니다. 이는 CLS에 영향을 주게됩니다. CLS란, 방문자에게 콘텐츠가 얼마나‌ 불안정한 지 측정하는 사용자 경험 측정 항목입니다. 쉽게 보자면 일정기간동안 레이아웃 이동이 없는 상태에서 발생하는 예상하지 않은 레이아웃 이동을 의미합니다.

Layout Shift를 일으키는 원인으로는 크기가 설정되지 않은 이미지와 폰트가 있습니다. (더 다양한 원인이 있을 수 있습니다.)

먼저 크기가 설정되지 않은 이미지를 살펴보겠습니다.

이미지에 크기를 설정하자

크기를 설정해야 한다고 했는데, 이는 사실 어떤 이미지 인가에 따라 설정 유무가 다릅니다.

다들 이미지를 어떻게 사용하시나요? 프로젝트의 규모와 목적에 따라 달라질 수 있겠지만, 저는 프로젝트의 크기가 커지는 것을 방지하기 위해 로컬 이미지는 거의 사용하지 않고 보통 웹 주소로 변환하여 외부에서 불러오는 방식으로 사용하는 편입니다. (물론, 이미지 크기 자체를 최적화해서 로컬에서 사용할 수도 있겠네요.)

“어떤 이미지 인가” 는 바로 외부와 로컬을 의미했습니다. 앞서 NextJS는 next/Image<Image> 컴포넌트를 제공한다고 했었습니다.

로컬 이미지

로컬 이미지를 사용한다면, NextJS는 자동으로 이미지의 높이와 너비를 결정하고, 이 높이와 너비는 이미지가 로딩될 때 Layout Shift를 방지하는 역할로 사용됩니다. 대체 높이와 너비만 설정한 건데 어떻게 Layout Shift를 방지하는 걸까요?

앞서, Layout Shift는 레이아웃이 밀려나는 현상이고 <Image>컴포넌트는 기본적으로 지연로딩이 적용된다고 했습니다.

하나의 예시를 떠올려볼까요?

위와 같은 레이아웃에서 페이지가 어떻게 로드될까요? <Image>는 지연 로딩이 적용되기에, 처음에 header - footer가 보이고 뒤늦게 이미지가 로드된 후 중간에 <Image>가 추가되어 보일 것 같습니다. 즉, 뒤늦게 로드되는 <Image> 때문에 레이아웃이 밀려나게 되는 것이죠.

결국 높이와 너비를 설정하는 것은, 해당 이미지 요소가 이 만큼의 공간을 차지하고 있다고 명시하는 것으로 볼 수 있고 이는 앞선 현상(레이아웃이 밀리는 현상)을 방지하는 역할을 하는 것입니다.

외부 이미지

외부 이미지는 로컬 이미지와 다르게 빌드 과정에서 NextJS는 외부 이미지에 접근할 수 없기 때문자동으로 높이와 너비를 설정하지 못합니다. 그렇기 때문에 수동으로 설정을 해줘야 합니다.

추가적으로…

앞서 높이와 너비를 설정한다고 했는데, 이는 이미지의 실제 높이와 너비를 의미하는게 아님에 유의해야 합니다. Layout Shift를 방지하기 위한 일종의 비율(가로:세로) 입니다.

NextJS Image Usage

폰트 렌더링의 적절한 전략을 채택하자

이미지에 관한 얘기를 굉장히 길게 늘여놨는데, 사실 저는 폰트가 Layout Shift의 주 원인이었습니다. 저는 next/font에서 제공하는 Google Font를 사용했습니다.

먼저 폰트 로드 속도를 확인 해보았습니다. 모두 불러오는데 95ms 가 소요되는 것으로 보아 로드 속도에 관한 문제는 아닌 것으로 파악됐습니다.

로드 속도가 아니라면 무엇이 문제일까?

먼저 폰트를 렌더링할 때 발생하는 문제 2가지에 대해서 알아보겠습니다.

종종 웹 폰트를 이용한다면, 폰트를 불러오기 전까지는 기본 폰트를 처음에 표시하고 폰트가 모두 불러와졌다면 기본 폰트에서 해당 웹 폰트로 빠르게 전환합니다. 이 과정에서 기본 폰트에서 웹 폰트로 변경되면서 텍스트가 깜빡이는 현상을 FOUT(Flash Of Unstyled Text) 라고 합니다.

이와 다르게, 웹 폰트가 다운로드되기 전까지 텍스트가 표시되지 않을 수 있는데 이는 FOIT(Flash Of Invisible Text)라고 합니다.

저는 기존에 아래와 같이 폰트를 사용하고 있었습니다.

font-display 속성이 보이시나요? 해당 속성을 통해서 폰트 렌더링 전략을 다르게 가져갈 수 있습니다.

  1. font-display: swap: 폰트가 로드될 때 까지 대체 폰트를 사용합니다. 방문자는 콘텐츠를 바로 읽을 수 있지만, 폰트가 모두 로드되어 변경될 때 레이아웃 변경이 발생할 수 있습니다. (FOUT에 대응된다고 볼 수 있겠네요)
  2. font-display: block: 폰트가 로드되기 전까지 텍스트를 표시하지 않습니다. (이는 FOIT에 대응된다고 볼 수 있겠네요)
  3. font-display: fallback: swapblock을 합한 것과 유사합니다. 폰트를 로드하는데 상대적으로 짧은 시간 약 100ms 동안은 block 속성을 띄며, 그 후에도 폰트가 로드되지 않았다면 swap처럼 대체 폰트를 사용합니다.
  4. font-display: optional: fallback과 유사합니다. 하지만 폰트를 로드하는데 상대적으로 짧은 시간인 약 100ms를 제공하며, 그 후에는 폰트가 교체되지 않습니다. (네트워크의 문제로 폰트를 로드할 수 없는 경우 브라우저가 폰트 로드 요청을 중단하도록 결정할 수 있는 기능이 있다고 합니다)
  5. font-display: auto: user-agent에 의해 렌더링 전략이 정의됩니다.

저는 기존의 swapblock으로 바꾸어 Layout Shift를 방지했습니다.

그렇다고 block을 사용하면 해결되겠네!" 는 아쉽게도 아닙니다. 폰트 개선에 관련한 포스팅을 찾아보면, swap으로 해결했다는 글도 보입니다.

결국, 해당 설정은 프로젝트의 기능과 목적에 맞게 유연하게 채택해야 합니다. “나는 폰트가 적용되지 않아도 돼. 그런데 텍스트는 무조건 보여야 해” 라는 입장이라면, swap이 적절하겠지요? 하지만, 저의 블로그는 딱히 초반에 텍스트가 잠깐 동안은 보이지 않아도 상관없어서, block을 채택했습니다.

Accessbility를 개선해보자

Accessbility 즉, 접근성은 왜 개선을 해야 할까요? “누구를 위한 것인지” 를 한 번 생각해보면 좋을 것 같습니다. 잘 모르겠다면, 다르게 접근해서 웹 접근성에 대해 생각해봅시다.

웹 접근성은 장애를 가진 사람들을 포함하여 모든 사용자가 웹 사이트와 웹 애플리케이션을 이해하고, 사용하며, 접근할 수 있도록 보장하는 것을 말합니다. 즉, 이는 다양한 사람들이 웹을 통해 정보를 얻고, 서비스를 이용하며, 커뮤니케이션을 할 수 있도록 하기 위함입니다.

시각장애인의 경우 화면을 눈으로 볼 수 없습니다. 그렇기에 스크린 리더 라는 보조 기술이 필요하며, 이 기술이 웹 콘텐츠를 올바르게 해석할 수 있도록 합니다. 또는 운용 가능성 측면에서도 생각해볼 수 있습니다. 예를 들어, 모든 기능은 키보드만으로도 접근 가능해야 하겠죠?

청각 장애를 가진 사용자들을 위한 접근성 고려 사항으로는 주로 오디오 콘텐츠에 대한 자막 제공, 비디오나 오디오 콘텐츠에 대한 텍스트 설명 또는 대체 수단 제공 등이 있습니다. 또는 오디오 알림을 비주얼 또는 텍스트 메시지로 대체하여, 청각 장애가 있는 사용자도 알림을 인지할 수 있게 하는 비주얼 알림이 있겠습니다.

결국, Lighthouse의 Accessbility가 바로 이 웹 접근성에 대한 항목인 것입니다.

어떤 것을 수정해야 할까?

다양한 요소가 있겠지만, 제가 마주친 문제로는 aria-label, alt, 그리고 색상 대비가 있었습니다.

색상 대비텍스트와 배경 간에 충분한 색상 대비를 의미하며, 저시력 사용자도 콘텐츠를 쉽게 볼 수 있도록 위함입니다.

추가적인 정보는 Lighthouse accessibility scoring을 참고해주세요.

마무리하며

이번 성능 개선은 몰랐던 부분을 알게되어 유익했던 시간이었습니다. 처음에는 배포환경과 개발환경에서 lighthouse의 점수 차이의 원인에 대해서 알지 못했고, 또 모바일은 왜 다르게 나오는지도 몰랐습니다. 또한 웹 표준 & 웹 접근성에 대해 알고 있었고, 이를 준수해야 함도 알고 있었지만 비교적 그러지 못해서 반성하게 되는 시간이기도 했습니다.

Lighthouse 점수를 측정해보면, 거의 대부분 Performance 영역에서 점수가 가장 낮고, Accessbility는 상대적으로 점수가 높이 나올 것 같습니다. (접근성은 imgalt를 붙여야 한다 등 공부를 한대로 작성하기만 하면 되지만 그럼에도 빼먹는 부분이 있을 것 같네요)

그렇다고 Performance에만 초점을 두지 말고, Accessbility도 신경을 써보면 좋을 것 같습니다. 결국 저희가 만드는, 만드려고 하는 서비스는 누구나 쉽게 사용할 수 있게 하는게 목표니까요.

다음 포스팅으로는 번들 사이즈를 개선해보겠습니다.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

coggiee
coggiee

Written by coggiee

0 Followers

Just doing

No responses yet

Write a response