목차
· 성능 최적화 방법
· 렌더링 최적화 (CSS 최적화, unused css의 제거, 간결한 스타일 작성, HTML 최적화, 복잡한 DOM 트리 지양, 애니메이션 최적화, JS 최적화)
· 로딩 최적화 (link와 script의 올바른 위치 지정, media 속성, Async / defer 사용)
· 이미지 최적화 (Picture 태그, 이미지 지연 로딩, 스프라이트 이미지)
· Webpack
· Gzip
· JS 압축
· CDN
· 캐싱
프로젝트를 진행함에 있어 기능을 구현하면서, 점점 규모가 커짐에 따라 코드의 양과 컴포넌트의 단위들도 비례하는 것을 발견하였다. 그리고, 중간중간 성능 향상을 위해 나름대로 이미지 크기를 줄이거나, 압축을 하는 등의 노력을 했지만 이미지 최적화 이외에도 다른 다양한 방법이 있을거라고 생각을 했다. IT 계열의 회사에서 재직을 하게 되면 웹 성능 최적화에 대한 이슈는 피할 수 없는 숙제이다.
내가 참고한 레퍼런스에 따르면, 다양한 방법이 있지만 간단하게 흛어봐도 아래와 같은 다양한 방법이 있다고 한다.
- style은 상단, js는 하단에서 불러온다
- 웹팩(Webpack) 사용
- js의 공백 줄이기
- html 작성시 불필요한 div를 제거
- css 최적화
- 리플로우, 리페인트(Reflow/Repaint)를 고려한 스타일 작성
- 사용하지 않는 css 제거
- 이미지 최적화
- picture, img 지연로딩 활용하기
- 스프라이트 이미지 사용
- 핵심적인 웹 지표(LCP,FID,CLS) 최적화
- 애니메이션은 js보다는 css로 사용한다
- transform과 translate로 구현할 수 있는 애니메이션을 js의 style.top과 style.left를 통해 구현을 하게 되면 리플로우가 발생하기 때문이다.
CSS 애니메이션과 JS 애니메이션 차이 자세히 보기
- 헤더에 만료기한 넣기
헤더에 만료기간을 넣는 이유는, 사이트에 처음 방문한 사람일지라도 여러 번의 HTTP 요청을 하게 될지도 모르지만 헤더 만료기간(Expires)를 이용함으로써 그 구성요소를 캐시에 저장할 수 있다. 이 브라우저 캐시를 이용하면 HTTP의 요청 수를 줄일 수 있다.
브라우저 캐싱 사용법 자세히 보기
- SEO (검색엔진최적화)
- CDN 사용
- Gzip 사용
- 파일 개수 줄이기
- 라이브러리 의존도 낮추기
성능 최적화 방법
브라우저는 상단부터 차례대로 로드 된다. HTML 파일을 읽어온 후, 위에서부터 아래로 한 줄씩 해석을 진행한다. 이 과정에서 CSS나 JS파일을 만나면 해당 파일을 해석하는 동안 웹 페이지 렌더링은 일시적으로 차단된다. 이때 마치 렉 걸린듯한 화면으로 보이는데 이러한 화면을 사용자가 보게되면 페이지를 이탈하는 문제점이 발생한다. 좋은 성능의 웹을 만들기 위해서 웹 성능 최적화는 필수로 거쳐야하는 과정이다.
마크업 개발자 입장에서 보는 성능 최적화는 크게 렌더링 최적화와 로딩 최적화 두 가지로 나눌 수 있다.
성능 최적화 = 렌더링 최적화 + 로딩 최적화
렌더링, 로딩 과정을 종합적으로 최적화 화면 웹 성능을 평가하는 핵심적인 웹 지표인(LCP, FID, CLS) 수치를 개선할 수 있다.
· LCP (Largest Contentful Paint): 페이지가 얼마나 빨리 로딩되는지 측정합니다. 화면을 가장 많이 차지하는 콘텐츠가 로딩되는 시점을 측정합니다.
· FID (First Input Delay): 응답성을 측정합니다. 링크를 누르는 등, 사용자가 페이지와 처음 상호 작용할 때 얼마나 시간이 걸리는지 측정합니다.
· CLS (Cumulative Layout Shift): 시각적 안정성을 측정합니다. 페이지가 로딩되면서 이미지나 텍스트 위치가 바뀌는 정도를 의미합니다. 버튼을 누르려고 했는데 갑자기 이미지가 로딩되면서 버튼이 밑으로 내려간 경험이 있으실텐데요, 이렇게 되면 CLS가 증가합니다.
더 자세히
https://jtravelwish.blogspot.com/2021/05/core-web-vitals-LCP-FID-CLS.html
렌더링 최적화
렌더링 차단 리소스란 브라우저의 렌더링을 막는 소스들로 일반적으로 css와 js 파일을 말한다 (모든 css와 js가 렌더링 차단 리소스인 것은 아니다) 웹 페이지 렌더링 최적화의 목표는 리플로우를 최대한 적게 발생시키면서, 빠르게 화면을 그리는 것이다.
CSS 최적화
리플로우, 리페인트(Reflow, Repaint)를 고려한 스타일 작성
브라우저의 스타일이 그려지는 순서이다. 이때 레이아웃의 넓이, 높이, 위치 등에 영향을 주는 css 속성을 변경할 경우 Layout부터 다시 그려지게 되는데 이를 리플로우(또는 레이아웃)라고 한다.
반면 레이아웃에 영향을 주지 않는 속성을 변경하면 레이아웃을 건너뛰고 페인트 작업부터 다시 수행하게 되는데 이를 리페인트라고 한다. 리플로우가 일어나면 브라우저가 전체 픽셀을 다시 계산해야하기 때문에 되도록 리페인트 속성을 사용해 스타일을 작성하는 것이 성능면에서 좋다.
· 리플로우(Reflow)를 발생시키는 속성 (레이아웃의 넓이, 높이, 위치 등에 영향을 주는 속성들)
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 ...
· 리페인트(Repaint)를 발생시키는 속성 (레이아웃에 영향을 주지 않는 속성들, 꾸미는 속성들)
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 / translate ...
사용하지 않는 CSS 제거
앞서 말했듯 CSS는 렌더링 차단 리소스이기 때문에 사용하지 않는 CSS는 제거하는 것이 좋다. Unused CSS는 구글 크롬 라이트하우스를 통해 확인할 수 있다. (VScode에서도 리액트 실행환경에서 확인 가능)
간결한 스타일 작성
복잡한 셀렉터 사용은 지양한다, CSS가 복잡하고 방대할수록 레이아웃을 그리는 데에 시간이 많이 소요된다. 선택자를 간결하게 사용하여 특이성을 낮게 유지하는 것이 바람직하다.
.mypage .mypage_item{...} /* 🔺 */
.mypage_item{...} /* ✅ */
위의 예시에서 .mypage .mypage_item의 경우 .mypage_item이 부모요소인 .mypage를 가지고 있는지 확인하기 위해 DOM을 거슬러 올라가는 시간이 소요된다.
HTML 최적화
인라인 스타일을 사용하지 않는다.
· html 요소에 style을 통해 인라인 스타일을 작성하면 불필요한 코드 중복이 발생하기 쉽다.
· 인라인 스타일은 웹 페이지가 그려지면서 레이아웃에 영향을 미치며 추가로 리플로우를 발생시킨다.
· 애초에 스타일은 스타일 시트에 작성하는 것이 표준에도 맞고, 유지보수 측면에서도 좋다
<div style="margin-top:20px;"></div> <!-- 🔺 -->
복잡한 DOM 트리 지양
DOM 트리가 깊고, 자식 요소가 많을수록 DOM 트리는 커진다. DOM 트리가 커지면 DOM 변경 시 계산해야하는 것이 많아진다.
· DOM이 작고 깊이가 얕을수록 계산이 빠르다.
· 불필요하게 감싸는 요소는 제거한다. (ex. 불필요하게 감싼 div wrapper)
애니메이션 최적화
· 애니메이션을 구현할 때에는 자바스크립트 api, 라이브러리보다 css를 통해 구현하는 것이 성능면에서 이득이다.
· transform은 리플로우와 리페인트 모두 발생시키지않고 합성만 발생시키는 속성이기 때문에 애니메이션에서 사용 시 렌더링 속도를 향상시킬 수 있다.
· position 설정 시 absolute나 fixed로 설정하면 주변 요소에 영향을 주지 않는다.
JS(JavaScript) 최적화
· 레이아웃 단계가 완료되기 전에 요소의 위치나 크기를 변경 후 바로 가져오려고 하면 강제로 레이아웃이 발생하는데 이것을 강제 동기 레이아웃이라고 부른다. 그리고 이 레이아웃을 반복적으로 발생시키는 것이 레이아웃 스레싱이다.
· JS 최적화의 에이아웃 스래싱과 강제 동기 레이아웃에 대한 설명은 아래 링크에서 더 자세히 알아볼 수 있다.
로딩 최적화
CSS는 head 내에, JS는 body의 하단에 위치시킨다.
· 웹 페이지가 로드되면 html과 css가 동시에 파싱된다. html과 css는 바로 눈에 보이는 시각적부분을 구현하기 때문에 빠르게 그려질 수록 좋다. 그래서 css는 head 내에서 임포트한다.
· 웹 페이지는 파싱을 진행하면서 script를 만나면 html 파싱을 멈추고 해당 파일을 다운로드한 뒤 실행한다. 따라서 js를 제외한 구조들의 로드가 끝나고 js가 들어오는 것이 좋다. 일반적으로 body 태그를 닫기 직전에 script를 임포트한다.
media 속성을 사용한다.
· media 속성을 사용하면 조건별로 css를 불러올 수 있다.
· 반응형 웹 제작시 유용하게 사용될 수 있다.
· 구글 크롬 라이트하우스(Lighthouse)는 media 속성이 없는 <link rel="stylesheet"/> 태그를 렌더 블로킹 리소스로 판단한다.
· media 속성이 없는 스타일시트는 해당 스타일시트를 브라우저가 해석하는 동안 화면에 스타일을 불러오지 않는다.
<link href="style.css" rel="stylesheet"> <!-- 🔺 -->
<link href="style.css" rel="stylesheet" media="(min-width:320px) and (max-width:768px)"> <!-- 브라우저의 넓이가 320이상 769미만일때 스타일시트 해석 -->
<link href="style.css" rel="stylesheet" media="print"> <!-- 프린트할때만 스타일시트 해석 -->
Async / defer
async와 defer 속성은 스크립트 파일을 병렬로 다운로드하게 해준다. 즉, 로딩시 웹 페이지 해석을 멈추지 않고 스크립트를 다운로드하는 것이다.
· async는 다운로드 후 즉시 실행한다.
· defer은 웹페이지가 모두 그려지고 DOM이 들어왔을 때 스크립트를 실행한다.
// 병렬 다운로드 & 즉시실행
<script async src="test.js"></script>
// 병렬 다운로드 & 지연실행
<script defer src="test.js"></script>
이 내용을 그림으로 확인 시 아래와 같다.
■ HTML 파싱
■ HTML 파싱 멈춤
■ Script 다운로드
■ Script 실행
- <script>
- <script async> : 병렬 다운로드 & 즉시실행
- <script defer> : 병렬 다운로드 & 지연실행
정리를 해보자면,
<script> - 반드시 순서대로 실행되어야할 때 (HTML 파싱이 되는 중간에, 스크립트가 다운로드되고 실행되는 과정에서 파싱이 멈춤)
<script async> - 빨리 실행되어야할 때 (HTML 파싱이 되는 중간에, 스크립트를 다운로드 하는 과정에서 파싱이 멈추지는 않지만, 실행되는 과정에서 파싱이 멈춤
<script defter> - 마지막에 파싱해도 상관없을 때 (HTML 파싱이 되는 과정과 동시에 스크립트 다운로드는 병렬적으로 수행하고, HTML 파싱이 끝나면 그때서야 스크립트 실행)
이미지 최적화
Picture 태그 사용하기
· picture 태그의 type 속성을 통해 사용자 환경에 맞는 이미지를 제공할 수 있다.
webp : IE 미지원, jpg/png 대비 30~70% 수준의 용량
avif : 크롬/삼성 인터넷 지원, 저용량이면서 고품질
<!--
브라우저가 avif를 지원하면, avif를 사용하고, 그렇지 않은 경우 webp,
둘다 지원하지 않을 경우에는 jpg 이미지를 사용한다 -->
<picture>
<source srcset="aaa.avif" type="image/avif">
<source srcset="aaa.webp" type="image/webp">
<img src="aaa.jpg" alt>
</picture>
· media 속성을 사용해서 브라우저 사이즈에 맞는 이미지를 제공할 수도 있다, media의 조건(해상도)에 따라 출력할 이미지를 지정할 수 있다.
<picture>
<source srcset="mob.webp" media="(max-width: 760px)"> <!-- 브라우저의 넓이가 760px이하일 때, mob.webp 이미지 출력-->
<img src="pc.webp" alt>
</picture>
· picture나 source 요소는 화면에 출력되는 요소가 아니다.
이미지 지연 로딩 활용하기
· loading 속성을 사용해서 이미지를 브라우저 화면에 지연/병렬로 로딩할 수 있다.
· 사용 가능한 값으로 auto, lazy, eager가 있다.
auto : 디폴트 값, loading 속성을 쓰지 않은 것과 같다. (구글 공식문서에는 auto가 명시되어있지 않다, 되도록 사용하지 말자)
lazy: 화상에 보이는 부분만 먼저 출력하고 화면 바깥쪽 이미지들은 로딩하지 않는다, 사용자가 화면을 위로 올리면 아래쪽에 있던 이미지가 올라오면서 로딩된다.
eager: 화면 위치에 상관없이 페이지가 로딩되자마자 이미지를 로드한다.
<img src="item.jpg" loading="lazy" alt>
스프라이트 이미지 사용
· 여러개 이미지를 하나의 이미지로 만들어서 CSS의 background-position 속성을 이용해 부분적으로 이미지를 사용하는 방법이다.
· 이미지 파일 개수 자체를 줄이므로 리소스 요청 개수를 줄일 수 있다.
.icon_rotate{...width:15px;height:15px;background-position:-23px -50px;}
.icon_del{...width:10px;height:13px;background-position:-65px -15px;}
...
Img 태그를 사용한다기보다는, css 스타일 시트에서 background-image로 지정을 하고 클래스 또는 아이디 값을 지정해주면서 그 값에 대한 CSS로 backgorund-position을 통해 하나의 이미지 파일에서 잘라오는 형식이다. 아래 링크에 사용에 대한 소스코드가 있으니, 참고하여 사용을 할 수 있다.
웹팩(Webpack) 사용
· 모듈 번들러 웹팩(Webpack)을 사용해서 css와 js 파일을 번들링(하나의 파일로 묶는 과정)을 통해 리소스 요청을 줄일 수 있다.
<html>
<head>
<link href="main.css" rel="stylesheet">
<link href="sub.css" rel="stylesheet">
<link href="sub2.css" rel="stylesheet">
</head>
<body>
<div id="content">
...
</div>
<script async src="sample1.js" type="text/javascript"></script>
<script async src="sample2.js" type="text/javascript"></script>
</body>
</html>
위와 같은 코드가 있다고 했을 때, 웹팩 등을 통한 번들링을 진행하게 되면 bundle.js와 bundle.css와 같이 아래의 코드로 번들링 되어진다.
<html>
<head>
<link href="bundle.css" rel="stylesheet">
</head>
<body>
<div id="content">
...
</div>
<script async src="bundle.js" type="text/javascript"></script>
</body>
</html>
Gzip 사용
· Gzip을 사용해 텍스트 기반의 리소스로 압축한다.
· 이미지, pdf 등은 이미 압축된 파일일 경우가 많아서 Gzip을 사용하지 않는다.
JS 압축
· UglifyJS 등을 사용해서 JS 파일을 압축한다.
· 불필요한 공백이나 줄바꿈을 제거해서 파일의 용량이 감소하며, 난독화를 하면 민감한 코드를 알아보기 어렵게 만들 수 있다.
CDN 사용
· CDN(Content Delivery Network)은 유저에게 많은 콘텐츠를 손실없이 빠르게 전달하는 서비스이다.
· 대용량 콘텐츠 다운 또는 스트리밍 등에 사용이 된다.
· 사용한 만큼 비용을 지불한다.
캐싱
· 캐쉬란 사용자가 요청하는 html, css, js, image 등을 첫 요청 시에 내려받은 뒤 특정 위치에 복사본을 저장하고, 이후 동일한 URI 리소스 요청이 왔을 때 이전에 저장해둔 파일을 사용해서 더 빠르게 로딩하는데 사용한다.
· 브라우저가 다운로드할 파일의 개수 자체를 줄이므로 시간적 측면에서 이득이 크다.
· 자세한 설명은 이곳에 잘 설명되어있다고 하는데, 이 부분은 차근차근 공부해보자 (백엔드 영역과 관련이 있기 때문에)
레퍼런스
하단의 블로그를 참조했고, 내가 궁금한 부분들은 글을 작성하면서 달아놓았다, 프로젝트를 진행하면서 직접 적용해보는 시간을 가져보도록 하자!
'취준적인' 카테고리의 다른 글
React JSX 개념에 대한 리마인드 (0) | 2023.03.05 |
---|---|
협업을 위한 eslint와 prettier (0) | 2023.02.28 |
SSR의 특징을 가진 Next.js를 왜 사용해요? (0) | 2023.02.26 |
React Hooks의 종류와 사용법 (0) | 2023.02.25 |
클래스형 컴포넌트와 함수형 컴포넌트의 차이 (0) | 2023.02.25 |
댓글