Lighthouse로 브라우저 성능 개선시키기
Frontend/Browser
· 2025-02-28

들어가며
브라우저로 성능 조회를 하다가 Chrome Dev Tools
상단 탭의 lighthouse
를 본 적이 있는가? 아마 Chrome과 Redux의 큰 장점인 Redux Devtools 확장 툴을 써본 사람은 한 번쯤 슥 지나치다 봤을테다. 오늘은 lighthouse
로 웹 애플리케이션의 브라우저 성능을 모니터링하고 개선해본다.
Lighthouse
내가 만든 웹 페이지의 첫 로딩 순간은 사용자의 최초 경험이기 때문에 신경을 많이 쓸 수 밖에 없는 부분이다. 시각적으로 빠르게 로드되는 페이지는 곧 사용자 만족으로 이어진다. 어떻게 하면 성능을 간단히 측정하고 개선시킬 수 있는지 Lighthouse를 통해 경험해보자.
성능(Performance)
Lighthouse 탭에 들어가서 페이지 로드 분석을 시작으로 우리가 얻을 수 있는 네 가지 지표는 성능, 접근성, 권장사항 그리고 검색엔진 최적화(SEO)이며, 이 지표들은 모두 웹 페이지의 성능과 사용자 경험을 직관적으로 보여준다. 이 중에서 성능은 사용자에게 얼마나 빠르게 화면을 그려주는지를 평가하는 지표다. 먼저 Lighthouse를 실행시켜보자. 맥북 기준으로 Command
+ option
+ j
로 개발자 도구에 접근하여 lighthouse
탭에 들어가서 페이지 로드 분석 버튼을 누르고 기다린다!
성능 분석 기다리는 중
잠깐! 정확한 성능 측정을 위해서 프로덕션 환경에서 테스트해야한다. 개발 환경에서의 성능은 프로덕션보다 낮게 나오기 때문!
성능 분석 결과 화면
분석 결과 위와 같이 여러 수치들을 마주할 수 있다. 가장 중요해보이는 성능은 총 다섯 가지 지표로 평가된다. 하나씩 살펴보자-
-
FCP(First Contentful Paint): 첫 번째로 컨텐츠가 화면에 그려지는 시간을 측정하는 지표이다. 텍스트, 이미지, 비디오, 배경 이미지 등이 처음으로 렌더링될 때 측정되며, 1.8초 이하가 이상적이다.
-
SI(Speed Index): 페이지가 시각적으로 완성되는 속도를 평가한다. 즉, 얼마나 빨리 콘텐츠가 눈에 보이게 되는지를 측정한다. 초기 Paint 후 시각적인 컨텐츠가 모두 로드될 때까지의 평균 시간을 측정한다.
-
LCP(Largest Contentful Paint): 사용자가 페이지를 로드할 때 가장 큰 컨텐츠가 화면에 표시되는 시간이다. LCP는 이미지와 큰 텍스트 요소의 로딩 시간에 영향을 받기 때문에 이미지 최적화와 서버 응답 속도 단축으로 개선시킬 수 있다. 2.5초 이하가 가장 이상적이다.
-
TBT(Total Blocking Time): 페이지가 상호작용 가능해지기까지의 총 차단 시간을 측정하는 지표이다. 개선 방법으로는 불필요한 JS 실행 줄이고 비동기 처리 활용하기 정도가 있다.
-
CLS(Cumulative Layout Shift): 페이지 로딩 중 예기치 않은 레이아웃 이동의 양을 측정하는 지표다. 컨텐츠가 예상치 않게 움직이는 것은 사용자의 경험을 해치기 때문에 CLS가 낮을수록 좋으며, 0.1 이하가 가장 이상적이다.
성능 계산기를 통해 세부 지표별 성능 점수를 계산할 수 있다
✔️ 브라우저 렌더링과 성능 지표와의 관계
Lighthouse 성능 지표는 브라우저 렌더링 단계와 밀접하다. 브라우저 렌더링 게시글에서 정리를 해두었지만 간단히 정리하자면, 렌더링 과정은 크게 DOM 생성
→ CSSOM 생성
→ Render Tree 생성
→ 레이아웃
→ 페인팅
→ 컴포지팅
단계로 나뉜다. 이 때, LCP, FID, CLS가 어느 단계와 밀접한지에 따라 성능 모니터링이 가능하다.
-
FCP (First Contentful Paint): DOM 생성 및 Render Tree 생성 후 초기 콘텐츠(텍스트, 이미지, 비디오 등)가 화면에 처음으로 표시되는 시점을 측정하기 때문에 첫 번째로 시각적인 콘텐츠를 페인트할 때와 밀접하다.
-
LCP (Largest Contentful Paint): 페이지 로딩이 사용자에게 충분한 정보를 제공하는 단계에 밀접하기 때문에 페인팅 단계에서 발생한다.
-
CLS (Cumulative Layout Shift): 예기치 않은 레이아웃 이동과 관련이 있기 때문에 동적 콘텐츠가 로드되거나 이미지 크기가 지정되지 않은 경우인 컴포지팅 및 페인팅 단계에서 측정된다.
접근성(Accessibility)
-
배포된 웹 사이트에 모든 사용자가 접근이 가능한지를 평가한다.
-
'모든 사용자'라는 것은 정확히는 장애를 가진 사용자도 쉽게 사용할 수 있는지에 대한 것이다. 따라서, 키보드만으로 조작이 가능한지, 스크린 리더 지원 여부 등을 점검하여 평가한다.
-
따라서,
키보드 네비게이션 지원
이나적절한 색 대비
등 으로 접근성 점수를 높일 수 있다.
권장사항 (Best Practices)
-
권장사항에서는 이 사이트가 최신 웹 표준을 얼마나 잘 준수하고 있는지를 평가한다.
-
어찌보면 배포를 위해 당연한 이야기지만
HTTPS를 사용
하여 웹사이트 보안을 강화한다거나 -
최신 Javascript를 사용
하거나브라우저 API 권장사항을 준수
하는 것으로 권장사항 점수를 높일 수 있다.
검색엔진 최적화 (SEO)
-
검색엔진이 웹 사이트 컨텐츠를 잘 이해하고 인덱싱하고 있는지에 대한 지표다.
-
적절한 메타 태그를 사용
한다거나헤더 태그 사용
또는이미지 태그 사용시 alt 속성을 활용
하는 것으로 점수를 높일 수 있다.
성능 점수 높여보기, 성능 최적화!
최적화 요소 반영 전 성능
여기까지 천천히 따라왔다면, 어느 정도 브라우저 성능을 위해 고민해야할 것들이 정리되었을테다. 위에 사진은 최적화 전의 블로그 성능이다. 엉망진창이다. 사용자가 가장 큰 컨텐츠를 볼 수 있는 시간까지(LCP)가 너무 길기 때문에 당연히 초기 컨텐츠가 전부 그려지는 시간(FCP)과 페이지가 완성되는 시간(SI)이 너무 느리다. 이를 개선하기 위해 필자는 성능 지표에 집중했다. 최적화를 위한 간단한 방법을 정리해보면,
-
이미지 최적화: 로드되는 리소스 중 가장 큰 비중을 차지하기 때문에 이미지 최적화로 점수를 가장 쉽게 높일 수 있다.
-
폰트 최적화: 두 번째로 중요한 시각적 요소이기에 무시할 수 없다. 페이지 로드 시 별도 서버(ex. Google)에 폰트를 요청하는지 검토하여 로딩 시간에 영향이 없게 할 수 있다. 불필요한 폰트 파일을 로드하는지도 검토해보자.
참고로, 해당 블로그는 SSG 렌더링 방식으로 구현되었기 때문에 다른 기술 블로그들이 개선하기 위해 사용한
dynamic import
는 적용해보지 못하였다.
이미지 성능 최적화
-
img
태그들 모두next/image
의<Image />
컴포넌트로 교체하기 -
<Image />
컴포넌트의lazy loading
과placeholder
,fill
등의 속성을 활용하여 로드 효율을 높이자. 이미지 렌더링 게시글에 더욱 자세한 내용을 다루니 참고하면 좋겠다.
1// 변경 전 2<img className={styles src={url} alt={url} /> 3 4// 변경 후 5<Image 6 src={url} 7 alt={url} 8 fill 9 priority 10 loading="eager" 11 placeholder="blur" 12 blurDataURL="/contents/sources/blank.png" 13 className={styles.thumbnail} 14/>
리소스 용량 최적화
-
로드 시간을 줄이고 싶은가? 리소스 크기를 압축된 포맷으로 그려보자.
jpg
,png
등 포맷을webp
와avif
포맷으로 변환하여 로드한다. 변경 전에는 메인 화면에서 로드된 리소스 총 용량은 2.7MB 였는데, 현재는 1.9MB로, 약 29.63% 가벼워졌다.(기특) -
next.config.js
에서 압축 포맷을 설정할 수 있다.
1const nextConfig = { 2 images: { 3 formats: ["image/avif", "image/webp"], 4 }, 5}; 6 7export default nextConfig;
avif로 변환된 이미지 리소스
폰트 최적화
-
불필요한 폰트가 로드되지 않나 검토하여 지운다. 당연한 얘기지만 이미지나 컴포넌트도 검토하시길(!)
-
필자는 Google Fonts를 사용중인데 외부
<link>
로 가져와서 로드한다. 이는 브라우저 로드 시 별도로 Google 서버에 폰트 요청을 해야하기 때문에 로딩 시간 지연을 야기한다. 따라서,@next/font/google
을 써서 서버 측 최적화를 유도하고 불필요한 폰트 파일 로드되는 일을 방지하자. -
display: swap
옵션으로 폰트가 로드되지 않았을 때는 기본 시스템 폰트가 표기되도록 하자. 기본 시스템 폰트는 요청 폰트가 로드된 직후에 제거된다.
1// 변경 전 2export default function RootLayout({ children }: { children: ReactNode }) { 3 return ( 4 <html lang="en"> 5 <head> 6 <link rel="icon" href="/icon.png" sizes="any" /> 7 <link 8 href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;700&family=Noto+Sans+KR:wght@400;500;700&display=swap" 9 rel="stylesheet" 10 /> 11 <link 12 rel="stylesheet" 13 href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" 14 /> 15 </head> 16 <body> 17 {...} 18 </body> 19 </html> 20 ); 21} 22 23// 변경 후 24import { Noto_Sans, Noto_Sans_KR } from '@next/font/google'; 25 26const notoSans = Noto_Sans({ 27 subsets: ['latin'], 28 weight: ['300', '400', '500', '600', '700', '800'], 29 display: 'swap', 30}); 31 32const notoSansKR = Noto_Sans_KR({ 33 subsets: ['latin', 'cyrillic', 'latin-ext', 'vietnamese'], 34 weight: ['300', '400', '500', '600', '700', '800'], 35 display: 'swap', 36}); 37 38export default function RootLayout({ children }: { children: ReactNode }) { 39 return ( 40 <html lang="en" className={`${notoSans.className} ${notoSansKR.className}`}> 41 <head> 42 <link rel="icon" href="/icon.png" sizes="any" /> 43 </head> 44 <body> 45 {...} 46 </body> 47 </html> 48 ); 49}
권장사항 개선해보기
-
이미지 최적화와 폰트 최적화로 성능 점수는 94에 도달했다. 허나 권장사항 점수는 여전히 78점.
-
상세 내용을 확인 결과
서드 파티 쿠키
경고가 나온다. 블로그에서 감지된 쿠키들은 주로 Cloudflare CDN과 Adobe Analytics 관련 쿠키들인데 사용자 추적에 사용되는 이 쿠키는 최신 웹 보안 정책에서 지양하고 있어 점수가 깎인 것. -
이는 내가 다른 링크를 입힌 아이콘을 외부에서 요청하는 부분에서 발생한 것으로 보인다.
cloudflare
을layout.tsx
에서<link>
로 요청하여 가져온다. 해결하기 위하여 로컬(/public
)에서 끌어오려 했으나css
파일을 공식 홈페이지에서 받아보려했으나 형식 지원이 되지 않아 결국react-icons
라는 라이브러리를 사용했다. -
react-icons
는Font Awesome
의 아이콘을 대체하여 더 가볍고 React 컴포넌트에서 관리하기가 쉬운 라이브러리다.
1// layout.tsx에서 아래 코드 제거 2<link 3 rel="stylesheet" 4 href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" 5/>
1// react-icons 적용 전 2<a 3 className={styles.toolTipLink} 4 href="/external-url" 5 ... 6> 7 <i className="fa-solid fa-t"></i> 8</a>; 9 10// react-icons 적용 후 11import { SiTistory } from 'react-icons/si'; 12 13<a 14 className={styles.toolTipLink} 15 href="/external-url" 16 ... 17> 18 <SiTistory /> 19</a>;
최적화 후 성능 평가
이미지 최적화, 리소스 크기 압축, 불필요 외부 리소스 제거와 폰트 최적화까지 진행하여 성능을 높이고, 추가적으로 브라우저 보안 관련 정책을 위한 권장사항을 개선시켰다. lighthouse가 없었더라면 이렇게 간편하게 모니터링을 하고 빠르게 추적할 수 있었을까?
최종 성능 평가
사실 이 최적화 과정은 "이런걸 해보았다"를 위한게 아니라 애초에 구현 단계에서 "이런 걸 신경써야겠구나"를 앞으로 의도해내기 위한 경험을 위한 글이라고 생각한다. 마무리 글과 함께 위 최종 블로그 성능 사진을 첨부하면서 게시글을 마친다.