최적화 (Optimization)

uxolrv·2022년 10월 7일
0

📌 최적화

주어진 조건으로 최대 효율을 낼 수 있도록 하는 것

컴퓨터 공학에서의 최적화란?
가능한 적은 리소스를 소모하면서 가능한 빠르게 원하는 결과를 얻을 수 있도록 하는 것

웹 개발에서의 최적화란?
주어진 조건 아래, 최대한 빠르게 화면을 표시하도록 만드는 것




💡 최적화의 필요성 및 효과

1️⃣ 이탈률 감소

최적화가 잘 되지 않은 웹 페이지는 화면 로딩 시간이 길어진다.
➡️ 사용자가 페이지를 이탈할 확률 증가

웹 사이트 성능 최적화를 통해 페이지 로딩 속도를 줄이면, 사용자의 이탈률을 효과적으로 감소시킬 수 있다.


2️⃣ 전환율 증가

전환율을 늘려 서비스 사용자를 늘리기 위해서는 이탈률을 줄여야 한다.

전환율: 웹 사이트를 방문한 사용자 중 회원가입, 상품 구매, 게시글 조회, 다운로드 등의 행위를 한 방문자의 비율


3️⃣ 수익 증대

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

실제로, 로딩 속도가 1초 빨라졌을 때 아마존 판매량은 1% 증가


4️⃣ 사용자 경험 (UX) 향상

최적화는 효과적인 UX 개선 수단!
페이지 로딩이 빠를 수록 UX는 향상되기 때문에 이미 페이지 로드 속도가 빠른 편이라고 해도 최적화를 통해 UX가 더욱 향상될 수 있다.

만약 로딩이 오래 걸릴 경우, 로딩 중임을 알려주는 UI를 표시하여 기다리게 하는 방법도 있지만 이는 최적화를 통해 페이지 로드 속도를 빠르게 하는 것보다 좋은 방법은 아니며, 이탈률 개선까지 이어지기는 어렵다.










📌 Optimization 기법

💡 HTML, CSS 코드 최적화하기

화면을 렌더링할 때 필요한 파일은 HTML, CSS
HTML 파일은 DOM 트리를, CSS 파일은 CSSOM 트리를 만들고 두 트리를 결합하여 렌더링할 때 사용한다.
두 트리 중 하나라도 변경되면 리렌더링이 발생하는데, 이때 트리의 크기와 복잡도에 따라 리렌더링에 소모되는 시간이 길어진다.

➡️ HTML, CSS 코드를 최적화함으로써 렌더링 성능 향상!




🔎 HTML 최적화 방법

1️⃣ DOM 트리 가볍게 만들기

DOM 트리가 깊을 수록, 자식 요소가 많을 수록 DOM 트리의 복잡도는 증가한다.
HTML 요소들의 관계를 잘 살펴보고, 불필요하게 깊이를 증가시키는 요소가 있다면 삭제!


2️⃣ 인라인 스타일 사용하지 않기

1) 인라인 스타일을 사용하면 클래스로 묶어서 한번에 작성해도 될 스타일 속성을 중복으로 작성해야 하는 경우가 생길 수 있다.
➡️ 불필요한 코드 중복은 가독성을 떨어트리고, 파일 크기를 증가시킨다.

2) CSS 파일은 단 한 번의 리플로우를 발생시키는 반면, 인라인 스타일은 리플로우를 계속해서 발생시켜 렌더링 완료 시점을 늦춘다.

3) 인라인 스타일은 웹 표준에 적합하지 않으므로, 지양해야 한다.




🔎 CSS 최적화 방법

1️⃣ 사용하지 않는 CSS 제거하기

CSS 파일의 모든 코드 분석이 끝난 후, CSSOM 트리가 생성되므로, 불필요한 CSS 코드는 CSSOM 트리의 완성을 늦춘다.

보통 해당 CSS를 사용하던 요소를 삭제하며 CSS 코드만 남는 경우가 많으므로, CSS 코드만 남지는 않는 지 확인하고 함께 삭제해야한다.


2️⃣ 간결한 셀렉터 사용하기

셀렉터가 복잡할수록 스타일 계산과 레이아웃에 시간을 더 많이 소모하므로, 최대한 간결한 셀렉터를 사용해야 한다.








💡 리소스 로딩 최적화 하기

HTML 파일에서 JavaScript 파일을 불러올 땐 <script> 요소를, CSS 파일을 불러올 땐 <link> 요소를 사용한다.
이때, 파일을 불러오는 위치에 따라 렌더링 완료 시점이 달라질 수 있다.




🔎 CSS 파일 불러오기

화면을 렌더링할 때 DOM 트리는 HTML 코드를 한 줄 한 줄 읽으면서 순차적으로 구성할 수 있지만, CSSOM 트리는 CSS 코드를 모두 해석해야 구성할 수 있다.

❗️ CSSOM 트리를 빠르게 구성할 수 있도록 HTML 문서 최상단에 배치 (<head> 요소 안에서 불러오기)


🔎 JavaScript 파일 불러오기

JavaScript는 DOM 트리와 CSSOM 트리를 동적으로 변경할 수 있다.

HTML 파싱 중에 <script> 요소를 만나는 순간, 해당 스크립트가 실행되며, <script> 요소 이전까지 생성된 DOM까지만 접근할 수 있다.

➡️ 만약 <script> 요소를 HTML 코드 중간에 넣는다면, 해당 요소 이후에 생성될 DOM을 수정하는 코드가 있는 경우, 화면이 의도한 대로 표시되지 않는다.

또한 스크립트 실행이 완료되기 전까지 DOM 트리 생성이 중단된다.
JavaScript 파일을 다운받아와서 사용하는 경우, 다운로드 및 스크립트 실행이 완료될 때까지 DOM 트리 생성이 중단
➡️ DOM 트리 생성이 중단된 시간만큼 렌더링 완료 시간이 늦어지게 된다.

❗️ JavaScript 파일은 DOM 트리 생성이 완료되는 시점인 HTML 문서 최하단에 배치








💡 브라우저 이미지 최적화 하기

페이지의 용량은 코드 데이터가 아닌, 이미지 파일과 같은 미디어 파일이 대부분!
➡️ 이미지 용량을 줄이거나, 요청 수를 줄이면 사용자 경험을 빠르게 개선할 수 있다.




🔎 이미지 스프라이트

여러 개의 이미지를 모아 하나의 스프라이트 이미지로 만들고, CSS의 background-position 속성을 사용해 이미지의 일정 부분만 클래스 등으로 구분하여 사용하는 방법

클라이언트에서 서버 요청이 증가할 수록 로딩 시간도 함께 증가
➡️ 웹 페이지를 로드하는 데 필요한 서버 요청 수를 줄이기 위해 이미지 스프라이트 기법을 사용할 수 있다.

👍 이미지 스프라이트 기법의 장점
1️⃣ 한번의 이미지 요청으로 대부분의 개별 이미지를 사용할 수 있기 때문에, 네트워크 로딩 시간을 단축시킬 수 있다.
2️⃣ 이미지 파일을 개별로 관리할 필요없이 스프라이트 이미지 파일만 관리하면 되므로 관리가 용이하다.




< 예시: 네이버 >

출처 : 네이버

하나의 이미지를 배경 이미지로 사용하되, 표시하고 싶은 부분에 맞춰 width, height, background-position 속성을 주어 아이콘을 표시




🔎 아이콘 폰트 사용하기

아이콘 사용이 많을 때에는 이미지가 아닌, 아이콘 폰트를 사용하여 용량을 줄일 수 있다.


✨ Font awesome 사용 방법

1️⃣ CDN으로 사용하기

  • Font Awesome 가입 시, 발급받은 키트를 HTML 파일에서 <head> 요소에 넣어주면 CDN으로 Font awesome 사용 가능

  • Font awesome 사이트에서 사용하고 싶은 아이콘을 찾아 사용할 환경에 맞는 코드를 복사하여 사용


2️⃣ Font Awesome 모듈 설치하기

  • Font Awesome을 다른 라이브러리처럼 설치해서 사용하는 방법

  • 리액트 환경에서 사용할 경우 다음과 같은 패키지 설치!

// 핵심 패키지 설치
npm i --save @fortawesome/fontawesome-svg-core

// 아이콘 패키지 설치 (유료 아이콘을 사용할 경우 추가 설치 필요)
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/free-regular-svg-icons
npm i --save @fortawesome/free-brands-svg-icons

// Font Awesome React 구성 요소 설치
npm i --save @fortawesome/react-fontawesome@latest
  • Font Awesome 사이트에서 사용하고 싶은 아이콘 정보를 확인한 후 알맞게 불러와서 사용

  • 이때, 아이콘 이름은 camelCase로 작성!








💡 WebP 또는 AVIF 이미지 포맷 사용하기

새롭게 등장한 이미지 포맷인 WebP와 AVIF를 사용하면 용량을 더욱 감소시킬 수 있다.

WebP: PNG와 비교해 용량의 26% 감소, JPEG와 비교했을 땐 25-35% 더 감소
AVIF: JPEG와 비교해 용량의 50%가 감소, WebP와 비교했을 땐 20% 감소

그러나, WebP와 AVIF 모두 비교적 최근에 등장한 이미지 포맷이기 때문에 JPEG 포맷처럼 모든 브라우저에서 호환되지 않는다는 단점이 있다.

➡️ 이 경우, HTML의 <picture> 태그를 이용하면 각 브라우저의 호환에 맞도록 분기 대체 가능!

<picture>: img 요소의 다중 이미지 리소스(multiple image resources)를 위한 컨테이너를 정의할 때 사용


< 예시 >

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

만약 접속한 브라우저에서 <source> 태그 내의 srcset에 정의한 WebP 포맷을 지원하지 않는다면 해당 <source> 태그는 무시된다.








💡 CDN 사용하기

CDN
세계 곳곳에 분산된 서버에 콘텐츠를 저장하여 제공하는 네트워크 시스템
콘텐츠를 빠르고 효율적으로 제공하기 위해 설계되었다.

네트워크 지연(latency)은 유저와 호스팅 서버간의 물리적 거리의 한계로 인해 발생할 수 밖에 없다.

CDN은 유저와 가까운 곳에 위치한 데이터 센터(서버)의 데이터를 가져온다.
➡️ 데이터가 전달되기 위해 거쳐야 하는 서버의 개수가 크게 줄기 때문에 로딩 속도가 빨라진다!








💡 캐시 관리

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




🔎 캐시 사용하기

✅ 캐시를 사용하지 않을 때

첫번째 요청: 이미지를 통째로 받아온다.
n 번째 요청: 똑같은 데이터를 다시 받아온다.

➡️ 똑같은 데이터를 반복해서 받아, 네트워크 리소스를 낭비하게 된다.



✅ 캐시를 사용할 때

서버에서 응답을 보내줄 때 이미지 파일과 함께 헤더에 Cache-Control을 작성해서 보내준다.
값은 60으로, 해당 이미지 파일은 60초 간 유효!


두 번째 요청부터는 캐시를 우선 조회하게 된다.
캐시에 데이터가 존재하면서, 유효하다면 캐시에서 해당하는 데이터를 가져와서 사용한다.


브라우저 캐시 활용으로 인한 효과

  • 캐시가 유효한 시간 동안 네트워크 리소스를 아낄 수 있다.
  • 파일을 다시 받아올 필요가 없기 때문에 브라우저 로딩이 빨라진다.
  • 로딩이 빨라진 만큼, 빠른 사용자 경험을 제공할 수 있다.




🔎 캐시 검증 헤더와 조건부 요청

캐시 유효 시간은 끝났지만, 서버에서 다시 받아와야 하는 파일이 캐시에 저장된 파일과 완전히 동일한 경우 똑같은 파일을 다시 받아와야 할까?

➡️ 이때, 서버의 파일과 캐시의 파일이 동일한지 확인해 재사용할 수 있는 HTTP 헤더인 캐시 검증 헤더조건부 요청 헤더 사용!



📎 캐시 검증 헤더

캐시에 저장된 데이터와 서버의 데이터가 동일한 지 확인하기 위한 정보를 담은 응답 헤더

  • Last-Modified: 데이터가 마지막으로 수정된 시점을 의미하는 응답헤더
    조건부 요청 헤더인 If-Modified-Since와 묶어서 사용

  • ETag: 데이터의 버전을 의미하는 응답 헤더
    조건부 요청 헤더인 If-None-Match와 묶어서 사용



📎 조건부 요청 헤더

캐시의 데이터와 서버의 데이터가 동일하다면 재사용하게 해달라는 의미의 요청 헤더

  • If-Modified-Since: 캐시된 리소스의 Last-Modified 값 이후에 서버 리소스가 수정되었는 지 확인하고, 수정되지 않았다면 캐시된 리소스를 사용

  • If-None-Match: 캐시된 리소스의 ETag 값과 현재 서버 리소스의 ETag 값이 같은 지 확인하고, 같으면 캐시된 리소스를 사용




✨ 캐시 검증 헤더와 조건부 요청 헤더 사용법

Last-Modified-If-Modified-Since, ETag-If-None-Match
두 쌍의 헤더 중 한 쌍만 사용할 수도 있지만, 보통 두 종류를 동시에 사용한다.

➡️ 둘 중 하나만 사용했다가 매칭되는 응답 헤더가 없는 경우, 재사용 가능한 경우에도 리소스를 다시 받아와야 하기 때문에



Last-ModifiedIf-Modified-Since

  1. 첫번째 요청을 보내고 응답을 받으면서 캐시 유효 시간이 60초인 이미지 파일을 받아온다.
    이 때, 서버의 파일이 마지막으로 수정된 시간을 의미하는 Last-Modified 헤더에 담긴 내용도 캐시에 함께 저장한다.



  1. 캐시 유효 시간인 60초를 초과한 후 두번째 요청을 보낸다 가정,
    유효 시간이 지났어도 해당 데이터를 재사용해도 되는 지 확인하기 위해 데이터 수정 여부를 확인하는 헤더 If-Modified-Since를 작성하고, 캐시에 함께 저장해놓았던 Last-Modified 값을 담아 요청을 보낸다.
    ➡️ 이 값을 이용해 서버 데이터의 최종 수정일과 캐시에 저장된 데이터의 수정일을 비교한다. (최종 수정일이 같아야 동일한 데이터!)



  1. 서버와 캐시의 데이터가 동일한 데이터임이 검증되었다면 서버는 "데이터가 수정되지 않았음"을 의미하는 304 Not Modified 응답을 보내주고, 캐시 데이터의 유효 시간이 갱신되면서 해당 데이터를 재사용할 수 있게 된다.




ETagIf-None-Match

  1. 첫 번째 요청을 보내고 응답을 받으면서 캐시 유효 시간이 60초인 이미지 파일을 같이 받아온다.
    이 때, 서버의 파일 버전을 의미하는 ETag 헤더에 담긴 내용도 캐시에 함께 저장



  1. 캐시 유효 시간인 60초를 초과한 후에 두 번째 요청을 보낸다 가정,
    유효 시간이 지났어도 해당 데이터를 재사용해도 되는 지를 확인하기 위해서 데이터 버전 일치 여부를 확인하는 헤더 If-None-Match를 작성하고 캐시에 함께 저장해놓았던 ETag 값을 담아 요청을 보낸다.
    ➡️ 이 값을 이용해 서버 데이터의 ETag와 캐시에 저장된 데이터의 ETag를 비교한다. (ETag 값이 같아야 동일한 데이터!)



  1. 서버와 캐시의 데이터가 동일한 데이터임이 검증되었다면 서버는 "데이터가 수정되지 않았음"을 의미하는 304 Not Modified 응답을 보내주고, 캐시 데이터의 유효 시간이 갱신되면서 해당 데이터를 재사용할 수 있게 된다.








💡 트리쉐이킹 (Tree Shaking)

나무를 흔들어 잔가지를 털어내듯, 불필요한 코드를 제거하는 것



🔎 트리쉐이킹을 해줘야 하는 이유

1️⃣ JavaScript 파일의 크기

규모가 큰 인터랙션을 자랑하는 웹 애플리케이션이 많아지면서 JavaScript 파일의 크기가 커졌으며, JavaScript 파일을 요청하는 HTTP 요청 수 또한 증가하였다.

➡️ 그만큼 파일이 오고 가는 동안 화면 표시가 늦어지며, 네트워크 속도가 느린 환경에서는 더 큰 병목현상을 유발한다.


2️⃣ JavaScript 파일의 실행 시간
JavaScript 파일이 실행되기 위해서는 파일 요청 → 다운로드 → 압축 해제 → 코드 파싱 → DOM트리 생성 → 컴파일 → 코드 실행 등의 여러 과정을 거쳐야 한다.

이처럼 거쳐야 하는 과정이 많아, JavaScript는 다른 리소스에 비해 실행까지 상대적으로 많은 시간이 소모된다.

➡️ JavaScript 파일의 크기가 커진 만큼, 파일의 실행 시간 또한 증가하였으며, JavaScript 파일 실행은 CPU에 크게 영향을 받아, 사양에 따라 소모 시간이 크게 차이나게 된다.




🔎 JavaScript 트리쉐이킹 수행 방법

웹팩 4버전 이상을 사용하는 경우 ES6 모듈을 대상으로는 기본적인 트리쉐이킹을 제공한다.
CRA을 통해 만든 React 애플리케이션도 웹팩을 사용하고 있기 때문에 트리쉐이킹 가능!


1️⃣ 필요한 모듈만 import 하기

라이브러리 전체를 불러오는 것이 아닌 필요한 모듈만 불러오면 번들링 과정에서 사용하는 부분의 코드만 포함하여 빌드하기 때문에 트리쉐이킹이 가능하다.


2️⃣ Babelrc 파일 설정하기

Babel
자바스크립트 문법이 구형 브라우저에서도 호환이 가능하도록 ES5 문법으로 변환하는 라이브러리

ES5 문법은 import를 지원하지 않기 때문에 commonJS 문법의 require로 변경시키는데, requireexport되는 모든 모듈을 불러오기 때문에, 트리쉐이킹이 불가능해진다.

➡️ 이를 방지하기 위해서 Babelrc 파일에 다음과 같은 코드를 작성해주면 ES5로 변환하는 것을 막을 수 있다.

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

❗️ modules 값을 true로 설정하면 항상 ES5 문법으로 변환하므로 주의!


3️⃣ sideEffects 설정하기

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

profile
안녕하세연🙋 프론트엔드 개발자입니다

0개의 댓글

관련 채용 정보