Section 4 Unit8 - [최적화] Optimization

BRANDY·2023년 3월 30일
0

최적화(Optimization)의 개념

최적화는 주어진 조건으로 최대 효율을 낼 수 있도록 하는것이며 컴퓨터 공학에서는 가능한 적은 리소스를 소모하며 가능한 빠르게 원하는 결과를 얻을 수 있도록 하는 것이다. 그렇다면 웹 개발에서의 최적화 방법과 그 필요성에 대해 알아보자.

최적화의 필요성 및 효과

1. 이탈율 감소
화면을 불러오는 시간이 길어지면 사용자가 페이지를 이탈할 확률을 높힌다. 웹 사이트의 성능 최적화를 통해 페이지 로딩 속도를 줄이면 사용자의 이탈률을 효과적으로 줄일 수 있다.

2. 전환율 증가
이탈율이 줄어들면 전환율은 높아질 확률이 커진다. 웹 사이트를 방문한 사용자 중 회원가입, 상품 구매, 게시글 조회, 다운로드 등의 비율이 커진다는 것인데 서비스 사용자를 늘리기 위해서는 이탈율을 줄이고 전환율을 노혀야 한다.

3. 수익 증대
이탈율 감소, 전환율 증가는 트래픽 증대 및 회원 수 증가로 이어지고 이는 곧 수익 증대를 의미한다.

4. 사용자 경험(UX) 향상
최적화는 효과적인 UX 개선 수단이다. 페이지 로딩이 빠르면 사용자 경험이 향상되고 모든 면에서 긍정적인 효과를 가져오기 때문에 웹 사이트 성능 최적화를 필수로 진행하는 것이 좋다.

최적화 기법

HTML, CSS 코드 최적화 하기

화면을 렌더링 할때는 HTML파일과 CSS파일이 필요하다. HTML파일은 DOM트리를, CSS파일은 CSSOM 트리를 생성하고 두 트리를 결합하여 렌더링할 때 사용하게 된다. 이 두 트리 중에서 하나라도 변경되면 리렌더링을 유발하며 트리의 크기가 크고 복잡할수록 소모되는 시간도 길어지게 된다. 따라서 HTML, CSS 코드를 최적화함으로써 렌더링 성능을 향상시킬 수 있다.

HTML 최적화 방법

1. DOM 트리 가볍게 만들기
HTML 요소들의 관계를 잘 살펴보고 불필요하게 깊이를 증가시키는 요소를 삭제한다.

// 수정 전
<div>
	<ol>
		<li> 첫 번째 </li>
		<li> 두 번째 </li>
		<li> 세 번째 </li>
	</ol>
</div>

// 수정 후 : 불필요한 div 요소 제거
<ol>
	<li> 첫 번째 </li>
	<li> 두 번째 </li>
	<li> 세 번째 </li>
</ol>

2. 인라인 스타일 사용하기 않기
인라인 스타일은 개별 요소에 스타일 속성을 작성해주는 것이기 때문에 클래스로 묶어서 한번에 작성해도 될 스타일 속성을 중복으로 작성하게 되는 경우가 생기는데 이런 중복 작성은 가독성을 떨어뜨리고 파일 크기를 증가시킨다. 또한 인라인 스타일은 리플로우를 계속해서 발생시켜 렌더링 완료 시점을 늦추고 웹 표준에 맞지 않으므로 지양하자.

//수정 전
<div style="margin: 10px;"> 마진 10px </div>
<div style="margin: 10px;"> 이것도 마진 10px </div>

//수정 후 : class와 CSS로 대체
<div class="margin10"> 마진 10px </div>
<div class="margin10"> 이것도 마진 10px </div>

.margin10 {
	margin: 10px;
}

CSS 최적화 방법

1. 사용하지 않는 CSS 제거하기
보통 해당 CSS 코드를 사용하던 요소를 삭제하면서 CSS코드를 삭제하지 않는 경우가 생기는데 항상 확인하고 함께 삭제하여 CSSOM 트리의 완성을 늦어지지 안게 하자.

2. 간결한 셀렉터 사용하기
셀렉터가 복잡해지면 스타일 계산과 레이아웃에 시간을 더 소모하게 되므로 간결한 CSS 셀렉터를 사용하자.

// 복잡한 CSS 셀렉터 예시
.cart_page .cart_item #firstItem { ... }

// 필요한 경우에는 어쩔 수 없지만, 가능한 한 간결하게 작성해줍니다.
.cart_item { ... }

리소스 로딩 최적화하기
HTML 파일에서 JavaScript 파일을 불러올 땐 <script> 요소를, CSS 파일을 불러올 땐 <link> 요소를 사용하게 되는데 이때 파일을 불러오는 위치가 어디인가에 따라서 렌더링 완료 시점이 달라질 수 있다.

1. CSS 파일 불러오기
DOM 트리는 HTML 코드를 한 줄 한 줄 읽으면서 순차적으로 구성할 수 있지만 CSSOM 트리는 CSS 코드를 모두 해석해야 구성할 수 있다. 따라서 가능한 빠르게 구성할 수 있도록 HTML 문서 최상단에 배치하는것이 좋다.

// CSS 파일은 HTML 파일 상단의 head 요소 안에서 불러오는 것이 좋습니다.
<head>
	<link href="style.css" rel="stylesheet" />
</head>

2. JavaScript 파일 불러오기
JavaScript는 DOM 트리와 CSSOM 트리를 동적으로 변경할 수 있다. HTML 코드 파싱 중에 <script> 요소를 만나는 순간 해당 스크립트가 실행되며, <script> 요소 이전까지 생성된 DOM까지만 접근할 수 있다. <script> 요소를 HTML 코드 중간에 넣는다면, 해당 요소 이후에 생성될 DOM을 수정하는 코드가 있는 경우에는 화면이 의도한 대로 표시되지 않게 됩니다.

또한 스크립트 실행이 완료되기 전까지 DOM 트리 생성이 중단되며 JavaScript 파일을 다운로드해서 사용하는 경우에는 다운로드 및 스크립트 실행이 완료될 때까지 DOM 트리 생성이 중단된다. DOM 트리 생성이 중단된 시간만큼 렌더링 완료 시간은 늦춰지게 되고 이러한 이유로 JavaScript 파일은 DOM 트리 생성이 완료되는 시점인 HTML 문서 최하단에 배치하는 것이 좋다.

<body>
	<div>...</div>
	...

	// JavsScript 파일은 body 요소가 닫히기 직전에 작성하는 것이 가장 좋습니다. 
	<script src="script.js" type="text/javascript"></script>
</body>

브라우저 이미지 최적화 하기
페이지의 대부분의 용량은 HTML/CSS/JS 코드 데이터가 아닌 이미지 파일 등의 미디어 파일이 차지하게 되는데 이 용량을 줄이거나 요청의 수를 줄이는것이 사용자 경험을 빠르게 개선할 수 있다.

1. 이미지 스프라이트
웹 페이지를 로드하는데 필요한 서버 요청 수 중 이미지를 줄이기 위해 이미지 스프라이트 기법을 사용한다. 이미지를 모아 하나의 스프라이트 이미지로 만들고 CSS의 background-position 속성을 사용해 이미지의 일정 부분만 클래스 등으로 구분하여 사용하는 방법이다.

하나의 이미지를 배경 이미지로 사용하되, 표시하고 싶은 부분에 맞춰 width, height, background-position 속성을 주어 아이콘을 만든다. 이는 많은 이미지 파일을 개별로 관리할 필요없이 특정 스프라이트 이미 파일만 관리하면 되어 관리가 용이한 장점이 있다. 실제로 네이버 예씨만 해도 이미지 스프라이트를 통해 120여개의 아이콘을 하나의 이미지로 모두 사용할 수 있게 되었다.

2. 아이콘 폰트 사용하기
아이콘 사용이 많을 때는 모든 아이콘을 이미지로 사용하는 것보단 아이콘 폰트를 이용하여 용량을 줄일 수 있다.
대표적인 아이콘 글꼴 서비스로는 Font AweSome 이 있다.

CDN을 이용하여 사용하는 방법

3. WebP 또는 AVIF 이미지 포맷 사용하기
이미지 최적화를 위해 전통적으로 사용하던 JPEG 또는 PNG 형식이 아닌 WebP 또는 AVIF 이미지 포맷을 사용하면 용량을 줄일 수 있지만 모든 브라우저에서 호환되지 않는다는 단점이 있다. 이 단점을 보완하기 위해 각 브라우저의 이미지 호환에 맞도록 HTML의 <picture> 태그를 통해 이미지 포맷을 최적화 할 수 있다.

다음과 같이 HTML 태그를 작성하 시, 만약 브라우저에서 <source>태그 내의 srcset에 정의한 WebP 포맷을 지원하지 않는다면 해당 <source> 태그는 무시된다.

<picture>
  <source srcset="logo.webp" type="image/webp">
  <img src="logo.png" alt="logo">
</picture>

CDN 사용하기
네트워크 지연(latency)는 유저와 호스팅 서버간의 물리적 거리때문에 발생할 수 밖에 없다. CDN은 이를 해결하기 위해 세계 곳곳에 분포한 분산된 데이터 서버를 통해 유저가 가까운 곳에 위치한 데이터 센터의 데이터를 가져올 수 있게 한다. 이는 로딩 속도의 향상을 시켜준다. CloudFront, CloudFlare와 같은 CDN서비스가 있다.

캐시 사용하기

캐시(Cache)는 다운로드 받은 데이터나 값을 미리 복사해놓는 임시 장소를 뜻하며 데이터에 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다.

서버에서 어떠한 이미지파일을 받아오는 요청을 보낸다고 하자. 첫번째 요청에서는 이미지를 받아 온 적이 없기 때문에 이미지를 통째로 받아온다. HTTP 헤더의 용량이 0.1MB, 이미지 파일의 크기가 1.0MB라면 총 용량은 1.1MB가 된다.
문제는 두번째 요청인데 똑같은 파일을 다시 받아오는 일이 발생한다. 받아온 이미지파일을 재사용할 수 있다면 헤더 용량만 0.1MB 받아오면 되는데 다시 1.1MB를 받아오는 비효율적인 상황이 발생하기 때문에 헤더에 Cache-Control을 작성하게 되면 캐시를 우선 조회하여 데이터가 존재하고 유효한 시간이라면 해당 데이터를 가져와서 사용하게 되며 유효한 시간이 지났다면 다시 받아오게 된다.

Cache-Control: max-age=60 //60초 동안 유효함

브라우저 캐시를 활용하게되면 네트워크 리소스를 아끼며 브라우저 로딩이 빨라지고 사용자 경험이 좋아진다.

캐시 검증 헤더 & 조건부 요청 헤더

만약 캐시 유효시간이 지났지만 동일한 데이터라면 재사용하는게 유리할 것이다. 이를 캐시 검증 헤더로 데이터가 동일한지 확인할 수 있다.

Last-Modified : 데이터가 마지막으로 수정된 시점을 의미하는 응답 헤더로, 조건부 요청 헤더인 If-Modified-Since 와 묶어서 사용합니다.
Etag : 데이터의 버전을 의미하는 응답 헤더로, 조건부 요청 헤더인 If-None-Match 와 묶어서 사용합니다.

또한, 데이터가 동일하다면 재사용 허용을 요청하는 헤더인 조건부 요청 헤더를 작성하여 데이터를 다시 받아올 수 있다.

If-Modified-Since : 캐시된 리소스의 Last-Modified 값 이후에 서버 리소스가 수정되었는지 확인하고, 수정되지 않았다면 캐시된 리소스를 사용합니다.
If-None-Match : 캐시된 리소스의 ETag 값과 현재 서버 리소스의 ETag 값이 같은지 확인하고, 같으면 캐시된 리소스를 사용합니다.

유효 기간이 지난 데이터를 받아 오기 위해 서버의 데이터가 마지막으로 수정된 시간을 의미하는 Last-Modified 헤더에 담긴 내용도 캐시에 함께 저장한다.
유효 기간이 지났어도 해당 데이터를 재사용해도 되는지 확인하기 위해 If-Modified-Since를 작성하고 캐시에 함께 저장해놓았던 Last-Modified 값을 담아 요청을 보낸다. 이 값들을 이용해 서버 데이터의 최종 수정일과 캐시에 저장된 데이터의 수정일을 비교하여 동일하다면 304 Not Modified(데이터가 수정되지 않았음)라는 응답을 받아 캐시 데이터의 유효 시간이 갱신되며 재사용할 수 있게 된다.

Etag와 If-None-Match
서버의 파일 버전을 의미하는 Etag 헤더에 담긴 내용도 캐시에 함께 저장하여 요청 헤더 If-None-Match(캐시에 저장된 데이터버전과 서버데이터버전이 일치하는지 여부 확인)를 작성하여 캐시에 함께 저장해놓았던 Etag값을 담아 요청을 보낸다.
이 값을 이용해서 Etag를 비교한뒤 동일하다면 304 Not Modified(데이터가 수정되지 않았음)라는 응답을 받아 캐시 데이터의 유효 시간이 갱신되며 재사용할 수 있게 된다.

보통은 두 쌍중의 헤더 중 한쌍만 사용할 수도 있지만 보통은 동시에 두 쌍을 사용하게된다.

트리쉐이킹(Tree Shaking)

불필요한 코드를 제거한다는 의미를 가진 트리쉐이킹은 애플리케이션의 규모가 커지면서 방대해진 코드의 불필요한 부분을 찾아내어 제거해준다.
특히 자바스크립트에서 트리쉐이킹이 필수적인데 웹팩 4버전 이상을 사용하는 경우 ES6 모듈 대상으로 기본적인 트리쉐이킹을 제공하며 React도 Create React App을 통해 생성한다면 사용할 수 있다.

효과적인 트리쉐이킹을 수행하는 방법에 대해 알아보자.

1. 필요한 모듈만 import 하기
라이브러리 전체를 불러오는것이 아니라 필요한 모듈만 불러와 코드의 크기를 줄이자

import { useState, useEffect } from 'react'

2. Babelrc 파일 설정하기
Babel은 자바스크립트 문법이 구형 브라우저에서도 호환이 가능하도록 ES5 문법으로 변환하는 라이브러리이며 ES5 문법은 import를 지원하지 않기 때문에 commonJS 문법의 require로 변경시키는데, 트리쉐이킹에서의 requireexport되는 모든 모듈을 불러오기 때문에 문제가 발생한다. 이를 방지하기 위해 Barbelrc파일에 다음과 같은 코드를 작성해주면 ES5 로 변환하는것을 막아준다.

{
  “presets”: [ 
    [
      “@babel/preset-env”,
      {
	    "modules": false
      }
    ]
 ]
}

모듈값이 true라면 항상 ES5 문법으로 변환하기에 주의하자.

3. sideEffects 설정하기
웹팩은 사이드 이펙트를 일으킬 수 있는 코드의 경우 사용하지 않는 코드라도 트리쉐이킹 대상에서 제외시킨다.

const crews = ['kimcoding', 'parkhacker']

const addCrew = function (name) {
	crews.push(name)
}

addCrew 함수는 함수 외부의 배열인 crews를 변경시키는 함수이기때문에 순수함수가 아니며 사이드 이펙트를 일으킬 수 있다고 판단한 웹팩은 이 코드를 제외시키지 않는다.
이럴 때 package.json 파일에서 sideEffects를 설정하여 사이드 이펙트가 생기지 않을 것이므로 코드를 제외시켜도 됨을 웹팩에게 알려줄 수 있다.

{
  "name": "tree-shaking",
  "version": "1.0.0",
  "sideEffects": false
}

또한 특정 파일에서만 발생하지 않을 것임을 알려줄 수도 있다.

{
  "name": "tree-shaking",
  "version": "1.0.0",
  "sideEffects": ["./src/components/NoSideEffect.js"]
}

4. ES6 문법을 사용하는 모듈 사용하기
라이브러리가 트리쉐이킹이 적용되지 않는다면 어떤 문법을 사용하는지 확인해볼 필요가 있다. 모두 ES5 문법을 사용하면 상관없지만 일부만 사용한다면 해당 모듈을 대체하면서 ES6를 지원하는 다른 모듈을 사용하는것이 유리하다.

Lighthouse

사이트를 검사하여 성능 측정을 할 수 있는 도구로 다양한 품질 검사를 할 수 있다.
크롬의 개발자 도구, Node CLI에서 실행할 수 있으며 알아볼 수 있는 분석 결과는 다음과 같다.

Node CLI에서 실행하는 방법

npm install -g lighthouse //-g는 전역 모듈로 설치하는 방법
lighthouse <url>
lighthouse --help //모든 옵션 보기

Lighthouse 노드모듈을 이용해 동적으로 프로그래밍하여 페이지 검사 리포트를 생성할 수도 있습니다. 이를 이용해 성능 테스트를 자동화할 수 있다.

profile
프런트엔드 개발자

0개의 댓글