1. 웹 성능 최적화란?
웹 프론트 환경에서는 각종 리소스들을 화면에 띄우고 업데이트 하는 과정에서 지속적으로 비용을 소모한다. 따라서, 웹 프론트엔드 성능 개선을 위해서 개발자는 최소한의 데이터로 빠른 시간에 사용자가 불편함을 느끼지 않는 최적의 화면을 띄워야 한다.
상품 관리자, 프로덕트 매니저에게 웹 페이지가 얼마나 빨리 로딩되는지가 서비스 사용자 경험(UX, User Experience)에 영향을 주며 매출 및 수익에 영향을 줄 수 있으므로 웹 성능 최적화를 대략적으로 이해하는 것이 중요하다.
결과적으로, 웹 프론트엔드 개발자의 웹 성능 최적화 작업으로서 사용자에게 좋은 사용자 경험을 제공할 수 있고 비즈니스의 성공과도 직결될 수 있다.
2. 최적화를 왜 해야할까?
· 비즈니스 관점
웹 성능 최적화를 한다는 것은 사용자에게 제공하는 웹 사이트를 빠르게 보여주는 것이다. 이것은 비즈니스에 영향을 주며 사용자에게 빨리 로딩되면 좋은 사용자 경험을 제공할 수 있다. 요즘 같이 빠르게 진행되는 시대에서 하얀 화면을 노출시키거나 로딩, 렌더링에 지연이 발생하면 사용자들은 자연스럽게 웹 사이트를 이탈하며 해당하는 서비스는 비즈니스 기회를 잃게 될 것이다.
구글의 모바일 부문에서 웹 페이지 로딩 시간과 이탈률에 대해 얘기하기를, 페이지 로딩 시간이 1초 -> 3초로 증가할 때 페이지 이탈률이 32%로 증가하며 1초 -> 10초로 증가되었을 땐 페이지 이탈률이 123%나 증가한다고 밝혔다.
· 검색 엔진 최적화 관점 (SEO, Search Engine Optimization)
구글에서는 웹 성능 지표*(웹 성능 최적화가 어느정도 되었는가)는 검색엔진 순위에 영향을 미친다고 발표했다.
*웹 성능 지표 : LCP, FID(First Input Delay, 최초 입력 지연 시간), CLS(Cumulative Layout Shift, 페이지 로드시 축척된 화면의 움직임)
· 사용자 경험(UX) 관점
사용자 경험 측면에서도 성능 개선은 중요하다. 웹 사이트가 초기에 빨리 로딩되어도 화면이 로딩되는 순서에 따라 좋지 않은 경험을 제공 할 수 있다. 이것이 웹 성능 수치에 영향을 끼칠 수 있는데 위의 웹 성능 지표 중 CLS라는 정량적인 수치로 측정이 가능하다.
로딩 속도 뿐만아니라, 사용자가 보는 화면(사용자의 시선에 따라 콘텐츠가 어떻게 로드 되는가?)이 사용자 경험에 큰 영향을 미친다. 일반적으로 사용자의 시선은 위 -> 아래로 이동하며 나라에 따라 다르지만 한국과 같은 경우 왼쪽 -> 오른쪽으로 시선이 이동하는게 자연스럽다. 이처럼 개발을 하며 위부터, 왼쪽부터 콘텐츠가 로딩되도록 하는 것이 자연스러운 사용자 경험을 제공할 것이다.
3. 웹 성능 지표에 대해서 알아보자
1) LCP (Largest Contentful Paint)
: 페이지에서 핵심 요소 콘텐츠 로딩 속도를 측정한다. 페이지에서 가장 큰 영역에 있는 이미지, 영상, 텍스트 등 해당 요소의 렌더링 성능을 기반으로 측정된다. 웹 페이지에서 가장 중요하고 사용자가 가장 처음 접하는 첫 요소이기 때문에 해당 영역이 늦게 나타나면 페이지 이탈률이 늘어날 수 밖에 없다.
2) FID (First Input Delay)
: 콘텐츠의 상호작용 속도를 측정한다. 사용자가 페이지와 처음 상호작용(링크 클릭 또는 버튼 클릭)시, 이에 대한 응답으로 브라우저가 이벤트를 처리하기 까지의 시간을 측정한다. Lighthouse에서는 TBT(Total Blocking Time)라는 지표와는 다르지만 거기서 성능을 측정할 때 TBT를 개선하는게 FID에도 개선 효과를 줄 수 있다.
3) CLS (Cumulative Layout Shift)
: 페이지 방문자의 시각적인 안정성을 측정한다. 사용자에게 웹 페이지가 표시되고 최종적으로 변화되는 정도를 측정한 수치이다. 이 지표는 사용자 경험에 중요한 영향을 끼친다. 페이지 렌더링 초기에 화면 전환이 심해지면 사용자가 원하는 동작을 할 수 없다.
4. 성능 최적화 방법
성능최적화는 렌더링 최적화와 로딩 최적화 2가지로 볼 수 있다. 이 과정을 종합적으로 최적화하면 위에서 알아본 웹 성능을 평가하는 웹 성능 지표인 LCP, FID, CLS 수치를 개선할 수 있다.
1) 렌더링 성능 최적화하기 - 브라우저가 화면에 그려주는 성능을 높이자.
웹 페이지의 렌더링 최적화 목표는 애니메이션(reflow, repaint)을 최적화 하는 것이다. 리플로우를 최대한 적게 발생시키며 빠르게 화면을 그려내야 한다. 따라서, CSS 최적화를 생각해보자. 이를 최적화 하기 위해서는 리플로우, 리페인트를 고려한 스타일을 작성해주는 것이 좋다.
- reflow : 브라우저가 문서 내 요소의 위치, 도형을 다시 계산하는 작업
- repaint : 문서 내 레이아웃에 영향을 주지 않는 스타일 속성이 변경되었을 때 발생
(1) DOM + CSS Object Model : HTML, CSS 가공해 DOM, CSSOM을 만든다.
(2) 2개를 조합해 렌더 트리 구조를 형성한다.
(3) 레이아웃 단계에서 위치, 요소에 대한 크기를 실제 계산하고, 어느 위치에 어느정도 사이즈로 있어야 하는지 브라우저가 계산한다.
(4) 페인트에서 브라우저가 색을 채워 넣는다.
(5) composite 단계 : 위 단계를 거쳐 쪼개진 각 레이어가 합성하는 과정이다.
위 전체 과정을 critical rendering path 또는 pixel pipeline 이라고 부른다. 사용자 액션 또는 여러 이유로 인해 애니메이션에 변화가 생기면 브라우저는 위 과정을 처음부터 반복한다. 따라서 불필요하고 과도한 반복이 생기면 웹앱의 렌더링 성능에 악영향을 미친다.
→ 이때, 렌더링 성능이 떨어진다는건? 애니메이션의 초당 프레임수가 60이 안되어 사용자가 보기에 부드럽지 않고 뚝뚝 끊기는 것처럼 보이는 현상, 이런 현상을 쟁크(jank) 현상이라고 하며 브라우저가 애니메이션을 초당 60 프레임으로 그려주지 못하는 문제이다.
그렇다면, 어떻게 개선해야할까?
브라우저에 부담을 덜 주는 방식으로 개선해보자. 위 과정에서 레이아웃, 페인트 과정을 건너 뛸 수 있다!
css 속성 중 GPU의 도움을 받을 수 있는 속성이 있는데 대표적으로 transform, opacity이다. 따라서 이 속성들로 커버할 수 있다면 잘 사용해주는 것이 애니메이션 성능에 좋다.
브라우저의 스타일이 그려지는 순서이며 이때 레이아웃의 넓이, 높이, 위치 등에 영향을 주는 css 속성을 변경할 경우 레이아웃부터 다시 그려지는데 이를 리플로우(또는 레이아웃)이라고 한다.
반면, 레이아웃에 영향을 주지 않는 속성을 변경하면 레이아웃을 건너뛰고 페인트 작업부터 다시 수행하는데 이를 리페인트라고 한다.
리플로우가 일어나면 브라우저가 전체 픽셀을 다시 계산해야 하므로 되도록 리페인트 속성을 사용해 스타일을 작성하는 것이 성능면에 좋다.
- 리플로우를 발생시키는 속성
position / width / height / margin / padding / display / top / left / right / bottom /
box-sizing / border-color / text-align / border / border-width /
font-family / float / font-size / font-weight / line-height / vertical-align /
white-space / word-wrap / text-overflow / text-shadow ...
- 리페인트를 발생시키는 속성
color / border-style / visibility / background / background-color /
background-image / background-position / background-repeat / background-size /
text-decoration / outline / outline-style / outline-color / outline-width /
border-radius / box-shadow ...
- 둘 다 발생하지 않는 속성
opacity / transform / cursor / z-index ...
추가적으로, 사용하지 않는 css를 제거하자
css는 렌더링 차단 소스이므로 사용하지 않는 css는 제거하자. Unuesd css는 구글 크롬 라이트하우스를 통해 확인가능하다.
- 구글 라이트하우스에서 performance와 Mobile에 체크 후 generate report 버튼을 클릭하면 해당 페이지 성능이 측정된다.
- 구글 라이트 하우스는 2KB 이상 사용되지 않은 css가 있을 때 오류로 표기한다.
- 그리고 스크롤을 내려 목록을 보면 remove unuesd css 항목을 확인할 수 있다.
- potential savings 항목을 통해 잠재적으로 어느정도 절약가능한지 보여준다.
간결하게 스타일을 작성하자
복잡한 셀렉터 사용을 지양하자. css가 복잡하고 방대할수록 레이아웃을 그리는데 시간이 많이 소요된다. 선택자를 간결하게 사용해 특이성을 낮게 유지하는게 좋다.
.container .item_box {...} /* 별로! */
.item_box {...} /* 간결하게 사용하자! */
html 최적화 하기
인라인 스타일을 사용하지 않는다.
- html 요소에 style을 통해 인라인 스타일을 작성하면 불필요한 코드 중복이 발생하기 쉽다.
- 인라인 스타일은 웹 페이지가 그려지며 레이아웃에 영향을 미치고 리플로우를 발생시킨다.
- 스타일은 스타일 시트에 작성하는게 표준이고 유지보수 측면에서도 좋다.
복잡한 DOM 트리 지양
- 돔 트리가 깊고 자식 요소가 많을수록 돔 트리가 커진다. 돔 트리가 커지면 돔 변경시 계산해야하는게 많아진다.
- 돔이 작고 깊이가 얕을수록 계산이 빠르다.
- 불필요하게 감싸는 요소는 제거한다. (불필요하게 감싼 div 등)
애니메이션 최적화하기
- 애니메이션을 구현할 때 자바스크립트 api, 라이브러리보다 css를 통해 구현하는 것이 성능면에서 더 좋다.
- transform은 리플로우와 리페인트 모두 발생시키지 않고 합성만 발생시키는 속성이다. 따라서 애니메이션에서 사용한다면 렌더링 속도를 향상시킬 수 있다.
- position 설정할 때 absolute나 fixed로 설정하면 주변 요소에 영향을 주지 않는다.
2) 로딩 성능 최적화하기 - 브라우저가 리소스를 불러오는 성능을 높이자.
(1) 블록 리소스(CSS, JS) 최적화
브라우저 로딩 과정에서 파싱 중 블록 리소스가 발생할 수 있다. CSS와 자바스크립트가 이에 해당하며 최적화의 첫 번째 단계는 이를 최적화 하는 것이다.
- CSS 최적화
렌더 트리를 구성하기 위해 DOM 트리와 CSSOM 트리가 필요하다. DOM 트리는 파싱 중 태그를 발견할때마다 순차적으로 구성가능하지만 CSSOM 트리는 CSS를 모두 해석해야 구성할 수 있다. 즉, CSSOM 트리가 구성되지 않는다면 렌더 트리를 만들지 못하고 차단된다는 것이다. 이 때문에 CSS는 렌더링 차단 리소스라고 하며 차단되지 않도록 항상 HTML 문서 최상단에 배치한다.
<head>
<link href="style.css" rel="stylesheet" />
</head>
- 자바스크립트 최적화
DOM트리와 CSSOM트리를 동적으로 변경할 수 있어 HTML 파싱을 차단하는 블록 리소스다. <script> 태그를 만나면 스크립트가 실행되며 이전까지 생성된 DOM에만 접근 가능하다. 그리고 스크립트 실행이 완료될 때까지 DOM 트리 생성이 중단된다. 외부에서 가져오는 자바스크립트 경우 모든 스크립트가 다운되고 실행될 때까지 DOM 트리 생성이 중단된다. 이런 이유로 자바스크립트도 렌더링 차단 소스라고 불리며 HTML 문서 최하단 </body> 직전에 배치한다.
<body>
...
<div>...</div>
<script src="app.js" type="text/javascript"></script>
</body>
만약 <head> 아래에 포함되어 있거나 HTML 내부 스크립트 태그가 포암되어 있어도 <script> 태그에 defer, async 속성을 명시하면 스크립트가 DOM 트리와 CSSOM 트리를 변경하지 않겠다는 의미여서 브라우저가 파싱을 멈추지 않는다. 단, 이 속성들은 브라우저 지원범위가 한정적이어서 사용에 유의해야 한다.
<html>
<head>
<script async src="index.js" type="text/javascript"></script>
</head>
<body>
...
<div>...</div>
</body>
</html>
(2) 이미지 스프라이트
하나의 페이지 내에서 아이콘마다 다른 이미지 파일을 사용하면 리소스 요청이 그만큼 증가한다. 이런 경우 스프라이트 기법을 활용해 요청을 1번으로 줄일 수 있다. 이미지 스프라이트는 여러 개 이미지를 1개로 만들고 CSS의 backgrount-position 속성을 사용해 부분 이미지를 사용하는 방법이다.
<button class="btn">버튼</button>
.btn {
background-image: url(../images/icon.png);
background-position: 10px 10px;
width: 20px;
height: 20px;
}
(3) CSS, 자바스크립트 파일 번들링하기
모듈 기반의 개발 방식 등장 전 분리된 여러 개 리소스 파일을 가져와 사용했다. 이런 경우에 webpack과 같은 번들러를 사용해 css, 자바스크립트 파일 요청을 줄일 수 있다. 번들러는 여러 개의 모듈 파일을 하나로 묶어 1개의 파일로 생성한다.
(4) 리소스 용량 줄이기
용량이 큰 리소스도 웹 페이지 로딩 시간을 느리게 한다. 리소스에 맞게 불필요한 데이터를 제거하고 압축해 사용하는 편이 좋다.
- 중복 코드 제거하기
자바스크립트 코드 중 자주 사용되는 코드들은 utils 파일로 정리해 사용하는게 좋다. 중복 코드로 인해 용량이 늘어나는 문제를 막는다.
마치면서
몇 개의 프로젝트를 진행하면서 웹 성능을 최적화하는데 관심이 많았다. 하지만 성능을 최적화 하는 방법들은 너무나 많았고 그때마다 찾아보면서 했었기 때문에 어느정도는 알고 있었지만 한 번 글로 통합해서 정리하는 편이 좋다고 느꼈다. 그리고 글을 쓰기 위해 더 다양한 웹 성능 최적화 방법을 알게 되었고 어떠한 이유로 사용되는지도 프로젝트를 진행하면서 했을 때보다 자세하게 알게 된 것 같다.
이론도 중요하지만 직접 하고 체감하는게 가장 중요하다고 생각하기 때문에 최적화가 필요한 시점에 다시 꺼내 읽어보면서 리팩토링하면 좋을 것 같아서 글을 쓰게 되었다.
매 프로젝트때마다 어떻게하면 좀 더 사용자들에게 좋은 환경을 제공할 수 있을지 고민하는데 성능을 최적화 하는 부분이 매우 중요하다는 것도 깨닫게 되었다.
참조
https://ui.toast.com/fe-guide/ko_PERFORMANCE
https://www.stevy.dev/frontend-web-performance-guide-1/
https://ko.javascript.info/script-async-defer
'개발' 카테고리의 다른 글
[React] typescript에서 interface와 type의 차이점을 알아보자 (0) | 2024.01.25 |
---|---|
[React/CRA] CRA + typescript + Eslint + prettier 초기 세팅하기 (1) | 2024.01.23 |
[자료구조] 해시 (Hash, Hash Table, Hash Function)와 자바스크립트에서의 해시맵 (4) | 2024.01.07 |
[자료구조] 이분 탐색/이진 탐색 (Binary Search) (1) | 2024.01.04 |
CSRF(Cross Site Request Forgery)와 XSS(Cross Site Scripting) (0) | 2023.12.02 |