[FE] 성능 최적화 - feat. 'Shall We Trip' 프로젝트

이정훈·2021년 11월 23일
118
post-thumbnail

프론트엔드 개발자에게 필요한 능력에는 어떤 것들이 있을까?
사용자 관점에서 애플리케이션을 설계하고, 디자이너와 백엔드 개발자 사이에서 일 하기에 커뮤니케이션 능력을 필요로 한다.
물론 위의 두 가지 역량도 굉장히 중요하지만 기술적인 부분이나 성능 개선을 위한 지식을 보유하는 것도 중요하다.
최근에 성능 최적화에 관심이 생겨 프론트엔드 개발자가 성능을 최적화하기 위해 적용할 수 있는 기술들에 대해 정리하고, 일부 기능은 최근에 진행한 여행 가계부 애플리케이션 'Shall we trip?'에 적용해 보려고 한다 🙂

Lighthouse와 개발자 도구의 network 탭을 사용해 성능을 측정하였다
사용자가 처음 애플리케이션을 사용할 때도 좋은 성능을 제공하고 싶었기 때문에 network 탭의 Disable cache 버튼을 체크해서 캐시를 사용하지 않는 환경에서 테스트를 진행하였다



성능 최적화 적용 이전 화면



Lighthouse 결과


FOUT(Flash of unstyled text) 현상에 의해 심각한 레이아웃 변화가 발생하고, boxicon도 마찬가지로 완전히 로드되기 전까지 화면에 제대로 보이지 않는 상황이다.
브라우저가 DOM 콘텐츠의 첫 번째 부분을 렌더링하는 데 걸리는 시간을 나타내는 지표인 FCP나, 뷰포트에 포함된 모든 HTML 요소들이 브라우저 화면에 렌더링 완료되는데까지 걸리는 시간을 나타내는 지표인 LCP도 개선이 필요함을 느꼈다
이제 성능 최적화를 통해 기존의 프로젝트를 개선해보자! 🤔



페이지 로드 최적화

블록 차단 리소스 최적화

HTML을 파싱할 때, CSS나 JavaScript 파일을 만나게 되면 파싱을 멈추고 해당 파일을 파싱하거나 다운로드 후 실행하게 되는데, 이처럼 HTML 파싱을 차단하는 요소를 블록 차단 리소스라고 한다.

  • CSS의 경우 <head> 태그 안에 임포트해야 하며, <script> 태그로 실행되는 js는 일반적으로 <body> 맨 하단에 위치시킨다.
  • js 파일은 deferasync 어트리뷰트를 이용해서 블로킹을 방지할 수 있다. 다만 defer의 경우 IE9 이하에서 문제를 발생시키고, async의 경우 DOM 생성 도중 파일을 다운로드 받고 실행까지 시켜서 문제를 발생시킬 수 있으므로 사용에 주의해야 한다.


CLS 최소화하기

Cumulate Layout Shift는 누적 레이아웃 변화의 약자다. 어떤 페이지에 들어갔을 때 갑작스럽게 발생하는 레이아웃 이동의 정도를 합산 이동 거리라는 개념을 도입해서 만들어낸 지표이다. 늦게 로딩되는 리소스에 의해 레이아웃이 변화하는 현상을 최소화하는 것은 중요하다.

레이아웃 변화를 일으키는 요소

  • 위치, 사이즈가 지정되어 있지 않은 이미지
  • 위치, 사이즈가 없는 광고나 iframe
  • 동적으로 변화하는 콘텐츠
  • FOUT, FOIT 방식으로 로딩되는 웹폰트
    FOUT(Flash of unstyled text) - 웹폰트가 로드되기 전까지 시스템 기본 글꼴을 보여주고, 로드되면 이후 대체
    FOIT(Flash of invisible font) - 웹폰트가 로드되기 전까지 텍스트 렌더링을 하지 않다가 이후 렌더링
  • DOM 업데이트 하기 전, 네트워크 응답을 기다리는 작업
  • Layout 변화를 일으키는 CSS 속성들을 사용할 때

여행 가계부 애플리케이션인 'Shall We Trip'에 CLS를 낮추기 위한 방법 고민했다.
가장 먼저 발견한 것은 폰트와 boxicon이 @import 방식으로 css 파일에 들어가 있었다는 것이다.
@import 방식은 성능을 생각한다면 좋은 방법이 아니다. link 태그는 로딩 시 병렬방식으로 다운로드 하는 반면, @import 방식은 직렬 방식으로 다운로드 하기 때문에 로딩시간이 길어지는 문제점이 발생한다.
또한 @import 방식은 edge 브라우저에서 제대로 동작하지 않는다는 문제점이 존재한다.
link 태그는 병렬방식으로 다운로드 하여 로딩속도가 빠르고, 여러개를 link 방식으로 다운로드 하더라도 익스플로러에서 순서가 동일하게 작동한다.


preload

preload는 브라우저에게 페이지에서 필요한 자원을 일찍이 fetch 하라는 지침이다.
폰트는 반드시 사용할 자원이기에 preload 속성을 넣어주었다.
그리고 폰트를 가져오는 사이트들에 preconnect 속성을 주어 브라우저가 외부의 도메인과 미리 연결을 할 수 있도록 했다.



CLS 최적화 이전 코드

@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;800&family=Noto+Sans+KR:wght@400;500;700&display=swap');
@import url('https://unpkg.com/boxicons@2.0.9/css/boxicons.min.css');

CLS 최적화 이후 코드

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
  rel="preload"
  href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;800&family=Noto+Sans+KR:wght@400;500;700&display=swap"
  as="font"
/>

CLS 최적화 결과



Lighthouse 결과

이전과는 다르게 다시 페이지를 불러와도 레이아웃 변화가 발생하지 않고, CLS가 엄청나게 개선되었다.
단순하게 폰트의 로드 방식만 바꿔줬을 뿐인데, 한 눈에 알아볼 수 있을 정도로 크게 개선되었다.



리소스 용량 줄이기

리소스의 용량을 줄임으로써 리소스 다운로드 시간을 최적화할 수 있다


JS 용량 최적화

  • 트리 쉐이킹
    번들 파일의 불필요한 코드를 제거해주는 것을 말한다. 사용하고자 하는 기능만을 임포트 한다면 파일 용량은 더욱 작아질 것이다.
  • 불필요한 코드는 제거하고 tab size 2를 사용한다.
  • 압축 및 난독화로 용량을 최소화한다.
    webpack 4 버전부터 mode라는 개념이 도입되었는데, production으로 설정해 놓으면 알아서 코드를 최적화해준다.
    production mode는 여러 플러그인들을 기본으로 적용해주는데, 그 중 TerserPlugin은 압축(Minify)과 난독화(Uglify)를 기본적으로 적용해준다.

압축 - 들여쓰기와 공백이 제거되고, 전체 코드가 한 줄로 병합된다. 원본 코드에서 들여쓰기, 공백, 콤마 등이 제대로 사용되지 않았다면 문제가 발생할 수 있다.
난독화 - JS 코드 자체를 분석하기 어렵게 만드는 과정. 난독화 단계를 높일수록 코드를 해석하고 실행하는 속도가 느려질 수 있다.


트리 쉐이킹 적용 이전 코드

import { debounce } from 'lodash';

network tab

트리 쉐이킹 적용 이후 코드

import debounce from 'lodash/debounce';

network tab


Lighthouse 결과

lodash에서 필요없는 코드를 제거하고 사용하는 기능만 받아서 JS 파일 용량을 최적화했다
그 결과 expense.bundle.js의 파일 용량이 약 절반으로 줄어들었고, 성능이 개선되었다



CSS 최적화

  • 간결한 선택자 사용
    브라우저는 셀렉터를 오른쪽에서 왼쪽으로 읽기 때문에 자식에서 부모로 거쳐서 올라가게 된다. 그래서 선택자가 복잡해질수록 비용이 많이 발생하게 된다.
  • CSS 파일 압축 - CssMinimizerWebpackPlugin 사용하기
    CSS 파일에서도 불필요한 빈칸, 공백 등을 제거해 압축시켜주는 작업을 진행한다.

웹팩을 사용해 JS, CSS 용량 최적화 이후


network tab

CSS 파일은 약 10%, expense.bundle.js 파일은 약 5분의 1 가량으로 파일의 크기가 줄어들었다
용량이 줄어든만큼 리소스 다운로드 시간도 줄어들어 더 빠른 렌더링을 기대할 수 있다



이미지, 미디어 최적화

  • 이미지는 png보다 jpg, jpeg의 이미지 크기가 더 작으므로 시각적인 품질 차이가 적다면 jpg, jpeg 확장자를 사용한다.
  • jpg, jpeg 대신 webp를 사용하면 평균 20~30% 정도 크기 감소를 시킬 수 있다. 다만 webp를 지원하지 않는 구버전 브라우저도 있기 때문에 주의해서 사용해야 한다.
  • 애니메이션이 적용된 요소의 경우, gif보다 video 태그로 mp4 파일을 사용하여 적은 용량의 리소스를 요청할 수 있다.
<video
  src={imageSrc}
  type='video/mp4'
  autoPlay
  muted
  loop
  playsInline
/>

위와 같이 작성하면 마치 GIF처럼 보이게 할 수 있다.



리소스 요청 개수 줄이기

리소스 요청 개수 자체를 줄여서 사용자에게 더 빠르게 페이지를 보여줄 수 있는 성능 최적화


이미지 스프라이트

여러 개의 이미지를 하나의 이미지로 합쳐서 관리하는 이미지
웹 페이지에 이미지가 사용될 경우 해당 이미지를 다운받기 위해 브라우저는 서버에 이미지를 요청하게 된다.

이미지 스프라이트를 사용하면 여러 이미지를 다운로드 받아도 서버 요청을 줄일 수 있다.
-> 로딩 시간 단축

스프라이트 이미지를 다운받아 background-position 속성을 조절하여 여러 이미지를 사용할 수 있다.


lazy-loading

웹 페이지를 열면 브라우저가 모든 이미지를 읽고 불러와서 렌더링 할 것이다.
만약 많은 이미지가 한 페이지에 있다면 그 페이지를 여는 시간도 오래 걸릴 것이고 모바일 환경에서 접속한다면 수많은 데이터를 낭비하게 될 것이다.
이러한 상황을 방지하기 위해 스크롤이 하단에 닿았을 때, 동적으로 데이터를 요청하고 화면에 렌더링하도록 구현하였다.
이와 같은 방식으로 구현하면 처음 화면을 렌더링 할 때 로딩 시간을 단축시킨다



최종 결과

Lighthouse 결과



느낀점

성능 최적화를 이론으로만 학습한 것이 아니라 실제로 진행했던 프로젝트에 적용하여 결과를 내었기에 더욱 뜻깊었던 시간이었다. 성능을 높이는 데 있어서 다양한 관점으로 바라볼 수 있는 시각을 키운 것 같고, 다음에 또 비슷한 문제를 겪게 된다면 이번에 했던 경험이 큰 도움이 될 것이다.

아쉬운 점이 있다면 성능 최적화 관점에서 가장 관심을 가졌던 웹 캐시를 적용하지 못했다는 것이다. 비교적 볼륨이 있고 웹 캐시만 따로 다뤄보고 싶어서 잠시 미뤄뒀는데, 조만간 웹 캐시에 대해 학습하고 적용하는 시간을 가질 예정이다. 또한 주로 SPA에서 사용하는 개념이라 이번 프로젝트에 적용하지 못한 코드 스플리팅에 대해서도 배워보고 싶다.




reference
https://coffeeandcakeandnewjeong.tistory.com/34
프론트엔드 성능 최적화

7개의 댓글

comment-user-thumbnail
2021년 11월 27일

많은 도움이 됐습니다. 좋은 글 감사합니다!

1개의 답글
comment-user-thumbnail
2021년 11월 27일

좋은 포스트 감사합니다 :)

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

좋은 글 감사합니다👍

1개의 답글
comment-user-thumbnail
2021년 12월 9일

멋있습니다 이정훈님

답글 달기