📖 이전 성능 개선 편을 참고해주세요.
1. 성능 개선 #1. LightHouse로 성능 파악해보기
2. 성능 개선 #2. 이미지 용량 줄이기
3. 성능 개선 #3. React Profiler로 컴포넌트 해부하기
4. 성능 개선 #4. LightHouse 진단해서 나온 문제점 개선하기
첫번째 컨텐츠가 페인트되는 지표를 바탕으로 개선점을 찾아주는 FCP관련 분석을 따로 필터링하여서 위의 사진과 같이 나타내어보았다.
자바스크립트 줄이기가 권장 사항으로 표기가 되었고 이것을 한번 알아보고 해결할 수 있는 부분이 있다면 개선해 나가려고한다.
여기를 클릭하면 구글이 제시하는 방법이 나오게된다. 자바스크립트를 줄이려면 파일을 번들링해야 된다. 흔히 우리가 아는 webpack 등이 그 예로 많이 등장한다.
현재 예제로 활용하고 있는 것은 localhost 즉 클라이언트 서버로 실행하였기 때문에 빌드 작업을 거치지 않아 번들링하지 않은 상태이기에 해당 수치로 나올 수 밖에 없다.
빌드 작업을 통하여 번들링한 제품은 더 좋은 수치로 표시된다.
그렇지만 빌드 하기 전에도 반영이 되는 부분이 몇 가지 존재한다.
우리가 협업을 진행할 때 다른 팀원들에게 자신이 작성한 코드를 읽히기 쉽게하기 위해 코드 사이의 공백이나 자바스크립트 문법에서 필요하지 않은 세미콜론 등을 사용하는 경우들이 있다.
또한 이 기능을 사용해 볼까?
라는 생각으로 테스트 변수나 함수를 작성, 혹은 라이브러리를 도입을 하고 사용하지 않고 그대로 방치하는 경우도 종종 있을 것이다. 특히 필자는 많이 그러는 것 같다 ㅎㅎ;;
개발을 생으로 처음 시작한 사람들은 C언어부터 발을 뗀다. 해당 언어에서는 코드의 기능이 끝나는 마지막에 세미콜론 (;) 을 작성한다. 자바스크립트에서는 C언어 인터프리트 과정과는 다르게 세미콜론을 빼도 오류가 나지 않는다. 다만 세미콜론을 쓰지 않는 파이썬과는 다르게 문단 끝에 사용 또한 가능하다.
검색으로 현재까지도 컨벤션에 따라서 쓰는지, 아니면 기용을 안하는지 스타일이 많이 다르다고 한다. 자바스크립트에서 세미콜론을 작성하는 이유는 해당 코드 단을 자바스크립트 인터프리트가 잘못 이해하는 경우를 방지하기 위해서라고 한다.
그래서 이번에는 세미콜론을 지우되, 긴 코드들이 나열되어있어 문단을 이해하지 못할 코드들을 제외하고 작업을 진행해 보려고 한다.
그전에 테스트로 진행하기 위해 VSCode Setting에서 세미콜론 전체를 일부 파일 확장자에서 지워보았다.
그 후 번들 파일 용량을 알아보았는데 이전과는 크게 달라지지 않는, 솔직히 말하면 달라진게 없다 ㅎ
아마 규모가 그렇게 크지 않아서 그런 것일 수도 있지만, 세미콜론 관련 문제는 그냥 컨벤션 주제가 99.99%가 아닐까? 라는 생각이 들게 되는 작업이었다.
하지만 그 다음에 다룰 주제는 확실한 차이를 가져와줄 수 있다.
이와 같이 setState 값은 사용하지 않고 stateValue 값만 사용하거나, 혹은 만들어놓고 사용하지 않는 데이터들..
또한, 설치하고 개편이나 테스트 실패로 사용하지 않는 라이브러리를 방치하는 등의 요소들은 추후 빌드되는 자바스크립트 파일에게 무거움만 안기게 된다.
그래서 클라이언트 서버를 실행시키면서 '~~' is assigned a value but never used
를 찾아 지우기 시작하였다.
또한 사용하지 않는 라이브러리를 하나하나 찾고 삭제하는 과정을 진행하였다. 예를 들면 스타일드 컴포넌트와 함께 SCSS를 사용하려다, 맥과 윈도우 사이에서의 node-sass 버전관련 이슈로 채택하지 않게 되었다. 이런 부분을 제거하면 된다.
열악한 사용자 경험에 큰 영향을 끼치는 것은 사실살 LCP라고 한다.
페이지에 진입할 때 첫번째로 가장 의미 있는 컨텐츠를 렌더링하는 데 걸린 시간을 잡기 때문에, 해당 컨텐츠가 렌더링이 오래걸리면 나머지도 차순으로 렌더링 되기 때문에 LCP를 빠르게 최적화 해주는게 그 무엇보다 중요하다.
다음은 LCP에 관한 감사 표시를 캡쳐한 사진이다.
FCP와 마찬가지로 자바스크립트 줄이기 라는 항목이 있다.
이 부분은 해결하였으므로, 다른 항목들을 우선적으로 보겠다.
사용되지 않는 자바스크립트항목은 20KB 이상의 미사용 코드가 포함된 모든 자바스크립트 파일에 플래그를 지정한다고 한다.
이번 LCP에서는 이 부분을 중점적으로 다뤄보려고 한다!
이번 역시 갓-크롬의 영문본을 번역하여 참고하였습니다.
Chrome Dev Tools의 적용 범위 (Coverage tab) 를 활용하여 현재 빌드 페이지의 불필요한 소스를 찾는 것이 가능하다.
방법은 개발자 창에서 Ctrl + shift + p
(vscode 설정 검색 여는 키와 동일하다)
그 후 아래 사진과 같이 적용 범위 보기 를 검색하면 하단에 탭이 생성되는데 거기서 범위 를 누르면 볼 수 있다.
위의 본문에서는 CSS 코드를 탐지하여서 불필요한 코드를 탐지하였다. 현재 사진의 bundle 파일 외에도 다른 JS 파일들이 검출되었지만, 굉장히 적은 사이즈에다가 kakao map 같은 외부 사이트 호출 JS라서 추가로 작업은 하지 않기로 하였다.
간과했다 😢
CRA로 React 프로젝트를 생성하면 무조건 Webpack 같은 번들러를 같이 설치해주는 줄 알고 있었는데, 사실상 까보니까 package.json에는 없다. 아무것도
그래서 우선 번들러를 추가로 설치 및 설정을 해보려고 한다.
Webpack에 관한 내용을 다룰 건데 이야기가 길어질 것 같아서 구간을 잘라서 포스팅 하려고 한다.
다음은 code splitting 으로 성능 개선하기까지 변화된 lighthouse 점수이다
최적화 이전.
code splitting 진행 과정 중의 성능 향상
Webpack 도입글
1편. Code Splitting, 번들링 툴 Webpack 도입하기
2편. 생각보다 어려운 Webpack 번들링 (loader 관련 에러)
3편. Webpack 만으로는 code splitting이 안된다. (React Lazy, Suspense)
이 부분에 대해서는 요기에서 도움을 받을 수 있었다. (설명이 아주 기깔남. 물론 번역본이지만)
이 페이지의 체대 페인트 이미지는 정적파일을 부른게 아닌, api에서 이미지 주소를 call한 것이다. 그러기에 이전에 했던 것처럼 이미지 리사이징이 애매하다.
호출에 의한 컨텐츠 표시는 서버 요청이기 때문에 서버에 대한 연결이 늦을 수록 LCP에 지장을 준다고 한다. 그래서 미리 페이지에게 "이 링크에서 데이터 쓸꺼야~" 라고 미리 언질을 해주면 브라우저는 미리 해당 링크에서 데이터를 받아올 준비를 한다고 한다.
index.html로 가서 다음과 같이 작성하면 된다.
그리고 브라우저마다 안되는 방식이 있을 수 있으니 두 가지 방법을 활용한다.
// rel="preconnect"를 사용하여 페이지가 최대한 빨리 연결을 구축할 것을 알림
<link rel="preconnect" href="https://example.com" />
// dns-prefetch를 사용하여 DNS 조회를 더 빠르게 해결
<link rel="dns-prefetch" href="https://example.com" />
img 태그에는 lazy loading 이 가능한 loading 이라는 태그가 있다.
그중에서 loading="eager"
를 사용하면 lazy와 정반대로 빠르게 로딩이 된다.
하지만 실험했을 결과 LCP가 정말 미미하게 개선되었다.. (절감효과 650ms -> 620ms)
사실 점수는 높다. 그래도 경험을 쌓기 위해 문제를 해결해보자.
CLS의 대부분의 요인은 웹 브라우저가 요소의 크기를 정확히 명시하지 않아 추측성으로 렌더링하기에 해당 문제점이 드러나는것으로 보인다. 아래 이미지를 보자
정말 작은 svg 파일같은 아이콘의 크기를 지정을 하지 않은 것이 문제였다.
그런데? 해결하는 것도 정말 쉽다. 제목대로 크기를 지정하면 된다.
<img src="/assets/spinner.gif" alt="Spinner" width="200" height="200" />
이렇게 img 태그 내에 width 값과 height 값을 명시하면 깔-끔하게 해결이 된다!
프로젝트 초기에 디자이너님이 Figma에 작성해주신 대로 UI를 작업하기 위해 'Pretendard'라는 폰트를 프로젝트 전역에 사용하기 시작했다. 그리고 처음에 호출한 방식은 Web Font를 CDN으로 호출하는 방식을 채택하였다.
그런데 이 방식에는 치명적인 오류가 있다. 바로 렌더링 시간을 꽤 잡아먹는다는 것인데..
아래 그림을 참조하길 바란다.
참고로 사용된 코드는 아래와 같다. 물론 스타일드 컴포넌트를 활용하기에, 전역으로 뿌려주기 위해 GlobalStyle를 호출하여 만들었다.
import { createGlobalStyle, ThemeProvider } from "styled-components";
@font-face {
font-family: 'Pretendard-Regular';
src: url('https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
}
해당 이미지는 cdn으로 네트워크에 접근하여 폰트를 브라우져에 다운받는 모습을 보여주고 있다.
물론 속도가 빠른 환경에서는 해당 최적화의 필요성을 크게 못느낀다는 느낌이기에, DevTools에서
같은 환경을 조성하기 위한 캐싱 제거 Disable cache, 속도 제어를 위해 Fast 3G 환경에서 실험을 진행해 보았다.
결과는 Size 1.1MB의 크기의 폰트를 14.31s 만큼의 시간을 두고 다운로드를 마친다.
이 뜻은 전체 서비스가 렌더링 완료 되기까지 14.31s의 시간이 필요하다는 것이다.
이번엔 woff, woff2, tff 파일을 모두 받아와서 활용했을 때의 모습이다.
이전 실행과는 달리 Size 805kB의 크기의 폰트를 12.51s 만큼의 시간을 두고 다운로드를 마친다.
차이는 약 200kB, 2초의 차이로 크게 두드러지진 않지만, 해당 폰트를 사용하여 글이 많이 나타나는 페이지를 로드 시, 체감나는 속도차이를 보여준다고 한다.
코드는 아래와 같이 작성하게 되었다.
const GlobalStyle = createGlobalStyle`
@font-face {
font-family: "Pretendard-Regular";
src: local("Pretendard-Regular"),
url('/assets/fonts/Pretendard-Regular.woff2') format('woff2'),
url('/assets/fonts/Pretendard-Regular.woff') format('woff'),
url('/assets/fonts/Pretendard-Regular.ttf') format('truetype');
font-display: swap;
}
먼저 위와 같이 GlobalStyle 로 전역 처리를 한 후 @font-face로 다운받은 font를 사용가능하게 만들었다.
여기서 달라진 부분이라면, font url 이 여러 개인 것과 주소이지 않을까.
우선 위에서 언급한데로 web에서 직접 접근하여 서버에서 받아오는 font 는 용량도 크고, 렌더링에 늦은 속도감을 더해주기에, 이번에는 직접 font 파일을 받아가지고 사용하였다.
woff2, woff 파일은 폰트 파일의 압축형 확장자로, 브라우져에서만 활용이 가능하다. webp랑 같은 역할을 하는 파일이라고 보면 된다. 그리고 현재 최신 브라우저에서 사용이 가능하고, woff보다 압축률이 높은 woff2를 먼저, 다음 woff, 그리고 실제 폰트 확장자인 tff파일 순서대로 선언을 해주었다.
이 순서에는 의미가 있다. 우리가 index.html에서 <link rel="preload" ... /> 하는 것처럼 이 또한 Preload의 효과를 지닌다.
그래서 처음에 렌더링을 시도할 때, 네트워크 창에서 제일먼저 woff2가 먼저 모습을 보이는 것을 확인 할 수가 있는데, 미리 브라우져에게 어떠한 폰트가 다운로드 될지를 미리 알려주고, 우선순위를 정해줘서 해당 파일을 좀 더 빠르게 로드가 가능해진다.
이 외에도 최적화에 관해서 공부해야 될 것이 너무 많다는 것을 느낀다.
그리고 현재 프로젝트를 단기간안에 최적화하기에도 너무 벅차다는 생각도 들게된다.
이번 최적화 작업을 거치면서 그와 관련된 많은 지식을 얻게되었고, 번들링을 통하여 생성된 파일들을 브라우저가 어떻게 읽고 파싱을 하여 레이어를 생성하고 그 레이어를 쌓아 올려 페인트를 진행하는 과정도 이해할 수 있는 좋은 경험이었다.
이번 포스트에 최적화를 진행한 모든 과정을 담을 수는 없었지만, 꾸준히 공부하고 새로 알게된 것들을 다시 리포스트하면서 성장하려고 한다!
Reference
https://developer.chrome.com/docs/lighthouse/performance/unminified-javascript/?utm_source=lighthouse&utm_medium=devtools
https://medium.com/@uk960214/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94-1-%EB%B2%88%EB%93%A4-%ED%81%AC%EA%B8%B0-%EC%A4%84%EC%9D%B4%EA%B8%B0-react-webpack-minify-code-splitting-e2391e7e5f1b
https://web.dev/optimize-lcp/?utm_source=lighthouse&utm_medium=devtools#optimize-when-the-resource-is-discovered