오늘의 주제는 최적화
다.
오랜만의 블로깅 시간이라 조금 여유로운 마음이 생기네...ㅋㅋㅋㅋ
페어 시간도 없겠다 천천히 정리해볼 생각이다 😎
최적화(optimization) :
허용된 자원의 한계 내에서 주어진 요구사항을 만족시키면서 최선의 결과를 얻는 과정이다. 수익과 관련되는 분야에서는 이익을 최대로 내는 과정을 말하기도 한다. 다양한 분야와 때에 따라 다르게 정의할 수 있고 물류(logistics), 설계(design) 문제 등에 응용된다.
[한국정보통신기술협회의 정보통신 용어사전]
➡️ 최적화는 보통 주어진 조건으로 최대 효율을 낼 수 있도록 하는 것을 의미
컴퓨터 공학에서의 최적화❓
가능한 적은 리소스를 소모하면서 가능한 빠르게 원하는 결과를 얻을 수 있도록 하는 것
(예시: 알고리즘 문제)
웹 개발에서의 최적화❓
주어진 조건 아래에서 최대한 빠르게 화면을 표시하도록 만드는 것
1️⃣ 이탈률 감소
화면 로딩에 시간이 오래 걸리면 사용자가 페이지를 이탈할 확률이 높아진다.
최적화를 하여 페이지 로딩 시간을 줄이면 이탈률도 감소할 것이다.
2️⃣ 전환율 증가
전환율이란 웹 사이트를 방문한 사용자 중 회원가입, 상품 구매, 게시글 조회, 다운로드 등의 행위를 한 방문자의 비율을 말한다.
최적화를 해서 이탈률을 줄이면 당연히 전환율도 올라간다.
3️⃣ 수익 증대
웹 사이트의 빠른 로딩 속도에 의한 이탈률 감소, 전환율 증가는 트래픽 증대 및 회원 수 증가로 이어지고 이는 곧 수익 증대에 영향을 미친다.
4️⃣ 사용자 경험(UX) 향상
페이지 로딩이 빠를수록 UX는 향상 ⬆️
만약 로딩이 오래 걸릴 경우, 스피너
, 프로그레스 바
, 스켈레톤
과 같이 로딩 중임을 알려주는 UI를 먼저 표시하는 방법도 있다.
하지만 이런 방법보다 최적화를 통해 페이지 로드 속도를 최대한 빠르게 하는 것이 UX에 좋다. 그리고 페이지 로드 속도가 개선되지 않는다면 이탈률 개선까지 이어지기는 어렵다고 보면 된다.
➡️ 이탈률 감소와 UX 향상 효과를 동시에 보기 위해서는 웹 사이트 성능 최적화를 진행하는 것이 가장 Good! 👍
HTML 파일은 DOM 트리를, CSS 파일은 CSSOM 트리를 만들고 두 트리를 결합하여 화면을 렌더링한다.
여기서 하나라도 변경되면 리렌더링을 하는데, 트리가 복잡할수록 더 많은 계산이 필요하기 때문에 소모되는 시간도 길어진다.
따라서 HTML, CSS 코드를 최적화함으로써 렌더링 성능을 향상시킬 수 있다.
1️⃣ HTML 최적화 방법
2️⃣ CSS 최적화 방법
HTML 파일에서 JavaScript 파일을 불러올 땐 script
요소를, CSS 파일을 불러올 땐 link
요소를 사용한다.
이때 파일을 불러오는 위치가 어디인가에 따라서 렌더링 완료 시점이 달라질 수 있다.
1️⃣ CSS 파일 불러오기
// CSS 파일은 HTML 파일 상단의 head 요소 안에서 불러오는 것이 좋습니다.
<head>
<link href="style.css" rel="stylesheet" />
</head>
2️⃣ JavaScript 파일 불러오기
<body>
<div>...</div>
...
// JavsScript 파일은 body 요소가 닫히기 직전에 작성하는 것이 가장 좋습니다.
<script src="script.js" type="text/javascript"></script>
</body>
페이지의 대부분의 용량은 HTML/CSS/JS와 같은 코드 데이터가 아닌 이미지 파일과 같은 미디어 파일이 차지 (전체 페이지 용량의 약 51%)
➡️ 이미지의 용량, 요청의 수를 줄이면 사용자 경험을 빠르게 개선할 수 있다.
1️⃣ 이미지 스프라이트
이미지 스프라이트 기법 :
여러 개의 이미지를 모아 하나의 스프라이트 이미지로 만들고 CSS의background-position
속성을 사용해 이미지의 일정 부분만 클래스 등으로 구분하여 사용하는 방법
하나의 배경 이미지를 사용하고 표시하고 싶은 부분에 맞춰 width
, height
, background-position
속성을 주어 아이콘을 만든다.
장점❗️
2️⃣ 아이콘 폰트 사용하기
아이콘 사용이 많을 때에는, 모든 아이콘을 이미지로 사용하는 것이 아니라 아이콘 폰트를 사용하여 용량을 줄일 수 있다.
대표적인 아이콘 글꼴 서비스로는 Font Awesome
이 있다.
Font Awesome
의 사용 방법 두가지를 소개한다.
CDN으로 사용하기
Font Awesome에 가입하면 발급해주는 키트를 HTML 파일에서 head
요소에 넣어주기만 하면 CDN으로 사용할 준비가 완료!
Font Awesome 사이트에서 사용하고 싶은 아이콘을 찾아서 코드를 복사하고 붙여넣기만 하면 사용할 수 있다.
Font Awesome 모듈 설치하기
camelCase
로 작성!3️⃣ WebP 또는 AVIF 이미지 포맷 사용하기
새롭게 등장한 이미지 포맷인 WebP 또는 AVIF를 사용하여 용량을 더욱 감소시킬 수 있다.
하지만 WebP와 AVIF 모두 비교적 최근에 등장한 이미지 포맷이기 때문에 JPEG 포맷처럼 모든 브라우저에서 호환되지는 않는다.
❗️그러나 HTML의 picture
태그를 이용하면 각 브라우저의 호환에 맞도록 분기를 대체할 수 있다.
picture
: img 요소의 다중 이미지 리소스를 위한 컨테이너를 정의할 때 사용한다.
만약 접속한 브라우저에서 source
태그 내의 srcset에 정의한 WebP 포맷을 지원하지 않는다면 해당 source
태그는 무시
이와 같은 속성을 이용하여 각 브라우저에 따라 이미지 포맷을 최적화 가능하다.
<picture>
<source srcset="logo.webp" type="image/webp">
<img src="logo.png" alt="logo">
</picture>
Content Delivery Network
네트워크 지연(latency)은 유저와 호스팅 서버간의 물리적 거리의 한계가 존재하기 때문에 필연적으로 발생한다.
CDN은 이를 해결하고자 세계 곳곳에 분포한 분산된 서버에 콘텐츠를 저장한다.
➡️ CDN은 유저가 가까운 곳에 위치한 데이터 센터(서버)의 데이터를 가져온다.그러므로 데이터가 전달되기 위해 거쳐야하는 서버의 갯수가 크게 줄기 때문에 로딩 속도가 빨라지는 것이다.
(CloudFront, Cloudflare와 같은 CDN 서비스 참고)
캐시(Cache) :
다운로드 받은 데이터나 값을 미리 복사해 놓는 임시 장소를 뜻하며, 데이터에 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용
캐시를 사용하지 않으면 첫번째, 두번째, 세번째 등....계속 똑같은 데이터를 받아와야한다.
이럴 때 캐시를 활용하면 이러한 리소스 낭비를 막을 수 있다.
서버에서 응답을 보내줄 때 이미지 파일과 함께 헤더에 Cache-Control
을 작성해서 보내준 것을 볼 수 있다. 값은 60으로, 해당 이미지 파일이 60초동안 유효하다는 것을 의미한다.
만약 유효 시간이 60초가 지났다면? 서버에서 다시 이미지를 받아오게 된다.
브라우저 캐시 활용 시 효과❗️
캐시 유효 시간이 지났다고해도 굳이 똑같은 파일을 다시 받아올 필요 없이 서버의 파일과 캐시의 파일이 동일한지 확인해서 재사용하면 더 효율적이지 않을까?
이런 상황에서 사용할 수 있는 캐시 검증 헤더와 조건부 요청 헤더를 확인해보자.
캐시 검증 헤더
캐시에 저장된 데이터와 서버의 데이터가 동일한지 확인하기 위한 정보를 담은 응답 헤더
Last-Modified
: 데이터가 마지막으로 수정된 시점을 의미하는 응답 헤더로, 조건부 요청 헤더인 If-Modified-Since
와 묶어서 사용
Etag
: 데이터의 버전을 의미하는 응답 헤더로, 조건부 요청 헤더인 If-None-Match
와 묶어서 사용
조건부 요청 헤더
캐시의 데이터와 서버의 데이터가 동일하다면 재사용하게 해달라는 의미의 요청 헤더
If-Modified-Since
: 캐시된 리소스의 Last-Modified
값 이후에 서버 리소스가 수정되었는지 확인하고, 수정되지 않았다면 캐시된 리소스를 사용
If-None-Match
: 캐시된 리소스의 ETag
값과 현재 서버 리소스의 ETag
값이 같은지 확인하고, 같으면 캐시된 리소스를 사용
➡️ 두 쌍중의 헤더 중 한 쌍만 사용할 수도 있지만, 보통 두 종류를 동시에 사용한다.
(둘 중 하나만 사용했다가 매칭되는 응답 헤더가 없는 경우에는 재사용할 수 있는 경우에도 리소스를 다시 받아와야 하는 경우가 생길 수 있기 때문)
트리쉐이킹(Tree Shaking) :
말 그대로 나무를 흔들어 잔가지를 털어내듯 불필요한 코드를 제거하는 것
웹 개발을 하면서 애플리케이션의 규모가 커지고, 다양한 라이브러리를 가져다 사용하게 되면 불필요한 코드들이 생긴다.
이런 코드들을 찾아내어 제거하면 웹 사이트 성능 최적화에 큰 도움이 된다.
특히 JavaScript는 아래와 같은 이유로 가능하면 트리쉐이킹을 해주는 것이 아주 좋다!
1️⃣ JavaScript 파일의 크기
요즘은 규모 있고 화려한 인터랙션을 자랑하는 웹 애플리케이션들이 많다. 웹 사이트에서 인터랙션이 많아졌다는 것은 그만큼 JavaScript의 비중이 높아졌다는 뜻.
JavaScript 파일 크기의 증가, 요청 횟수의 증가는 그만큼 파일이 오고 가는 동안 화면 표시가 늦어진다는 것을 뜻하고, 네트워크 속도가 느린 환경에서는 더 큰 병목현상을 유발한다.
➡️ 따라서 트리쉐이킹을 통해 파일 크기를 가능한 줄이는 것이 최적화에 많은 도움이 되는 것이다.
2️⃣ JavaScript 파일의 실행 시간
JavaScript 파일이 실행되기 위해서는 여러 과정을 거치게 된다.
컴파일 과정까지 거쳐야지 코드를 실행할 수 있다. 이처럼 거쳐야 하는 과정이 많기 때문에 JavaScript는 실행까지 상대적으로 많은 시간을 소모하게 된다.
➡️ 이러한 상황을 최대한 줄이기 위해서라도 트리쉐이킹을 통한 최적화가 필요
웹팩 4버전 이상을 사용하는 경우에는 ES6 모듈(import, export를 사용하는 모듈)을 대상으로는 기본적인 트리쉐이킹을 제공한다.
Create React App을 통해 만든 React 애플리케이션도 웹팩을 사용하고 있기 때문에 트리쉐이킹이 가능하다.
1️⃣ 필요한 모듈만 import 하기
import
구문을 사용해서 라이브러리를 불러와서 사용할 때, 라이브러리 전체를 불러오는 것이 아니라 필요한 모듈만 불러오면 번들링 과정에서 사용하는 부분의 코드만 포함시키기 때문에 트리쉐이킹이 가능해진다.
2️⃣ Babelrc 파일 설정하기
Babel은 자바스크립트 문법이 구형 브라우저에서도 호환이 가능하도록 ES5 문법으로 변환하는 라이브러리이다.
이 때 ES5문법은 import
를 지원하지 않기 때문에 commonJS 문법의 require
로 변경시키는데, require
는 export
되는 모든 모듈을 불러오기 때문에 필요한 모듈만 불러오기 위한 코드를 작성해도 소용이 없다.
이를 방지하기 위해서 Barbelrc 파일에 다음과 같은 코드를 작성해주면 ES5로 변환하는 것을 막을 수 있다.
{
“presets”: [
[
“@babel/preset-env”,
{
"modules": false
}
]
]
}
3️⃣ sideEffects 설정하기
웹팩은 사이드 이펙트를 일으킬 수 있는 코드의 경우, 사용하지 않는 코드라도 트리쉐이킹 대상에서 제외시킨다.
이럴 때 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를 지원하는 다른 모듈을 사용하는 것이 트리쉐이킹에 유리하다.
ES6 문법을 사용하는 모듈을 사용하면 필요한 부분만 import 해서 사용하지 않는 코드는 빌드할 때 제외할 수 있기 때문이다.
Lighthouse :
구글에서 개발한 오픈소스로서 웹 페이지의 품질을 개선할 수 있는 자동화 툴
Lighthouse는 성능, 접근성, PWA, SEO 등을 검사하며 이를 이용해 사용자는 어떤 웹페이지든 품질 검사를 할 수 있다.
Lighthouse는 Chrome DevTools부터 CLI, 노드 모듈 등 다양한 경로를 통해 사용할 수 있으며, 검사할 페이지의 url을 Lighthouse에 전달하면 Lighthouse는 해당 페이지에 대한 여러 검사를 실행한다.
그 후, 검사 결과에 따른 리포트를 생성하고 개발자는 해당 리포트를 통해 점수가 낮은 지표에 대해 개선을 도모할 수 있다. 또한 각각의 지표가 왜 중요한지, 어떻게 개선할 수 있는 지에 대한 레퍼런스도 리포트에서 참고할 수 있다.
1️⃣ Performance
Performance 항목에서는 웹 성능을 측정
2️⃣ Accessibility
Accessibility 항목에서는 웹 페이지가 웹 접근성을 잘 갖추고 있는지 확인
3️⃣ Best Practices
Best Practices 항목에서는 웹 페이지가 웹 표준 모범 사례를 잘 따르고 있는지 확인
4️⃣ SEO
SEO 항목에서는 웹 페이지가 검색 엔진 최적화가 잘 되어있는지 확인
5️⃣ PWA (Progressive Web App)
PWA 항목에서는 해당 웹 사이트가 모바일 애플리케이션으로서도 잘 작동하는지 확인
1️⃣ First Contentful Paint
➡️ FCP는 성능(Performance) 지표를 추적하는 메트릭
FCP는 사용자가 페이지에 접속했을 때 브라우저가 DOM 컨텐츠의 첫 번째 부분을 렌더링하는 데 걸리는 시간을 측정
즉 사용자가 감지하는 페이지의 로딩속도를 측정할 수 있다. 우수한 사용자 경험을 제공하려면 FCP가 1.8초 이하여야 한다.
페이지의 이미지와 canvas
요소, SVG 등 모두 DOM 콘텐츠로 구분되며 iframe
요소의 경우 이에 포함되지 않는다.
2️⃣ Largest Contentful Paint
➡️ LCP는 뷰포트를 차지하는 가장 큰 콘텐츠(이미지 또는 텍스트 블록)의 렌더 시간을 측정
주요 콘텐츠가 유저에게 보이는 시간까지를 가늠할 수 있다.
3️⃣ Speed Index
➡️ 성능(Performance) 지표를 추적하는 메트릭
Speed Index는 페이지를 로드하는 동안 얼마나 빨리 컨텐츠가 시각적으로 표시되는 지를 측정
4️⃣ Time to interactive
➡️ TTI는 페이지가 로드되는 시점부터 사용자와의 상호작용이 가능한 시점까지의 시간을 측정
5️⃣ Total Blocking Time
➡️ TBT는 페이지가 유저와 상호작용하기까지의 막혀있는 시간을 측정
대부분의 사용자는 0.05초가 넘는 작업에는 응답이 올때까지 계속 키보드를 두드리거나 마우스를 클릭하기 때문에 페이지가 느리다고 인식한다.
이를 개선하기 위한 지표가 TBT!
6️⃣ Cumulative Layout Shift
➡️ CLS는 사용자에게 컨텐츠가 화면에서 얼마나 많이 움직이는지(불안정한 지)를 수치화한 지표
이 지표를 통해 화면에서 이리저리 움직이는 요소(불안정한 요소)가 있는 지를 측정할 수 있다.
Lighthouse는 성능을 측정할 뿐 아니라 무엇이 시간을 많이 소모하는지, 어떻게 개선하여 최적화를 할 수 있을지 해결책도 제시해준다.
Opportunities 항목을 확인하면 각 메트릭별 문제를 확인할 수 있다.
각 항목을 열어보면 해당 문제에 대한 상세 설명과 함께 어떤 코드, 어떤 화면에서 문제 상황을 발견했는지 확인할 수 있기 때문에 최적화의 방향을 잡기 아주 좋다.
Lighthouse를 이용해서 웹 사이트 성능 최적화 뿐만 아니라, 웹 표준, 웹 접근성, SEO도 개선시켜 보자! 😆