1. 들어가며…
프론트엔드에서 Vue나 React와 같은 Virtual DOM 프레임워크의 사용이 활발하다.
그리고 이러한 프레임워크들은 페이지마다 반복 작성했던 코드를 재사용 컴포넌트로 분리할 수 있다.
그래서, 아토믹 단위로 요소를 관리하기 쉬워졌다.
재사용 가능한 요소인 헤더, 네비게이션, 아코디언, 그리고 비즈니스 로직에서 반복되는 UI 등을 컴포넌트로 분리할 수 있다.
물론, SVG 아이콘도 별도의 컴포넌트로 관리할 수 있다.
❓ 그런데, 과연 SVG 아이콘 파일을 컴포넌트로 만들어야 할 필요가 있을까?
소규모 프로젝트에서는 그 필요성을 느끼지 못할 수도 있다. 하지만 실제 서비스되는 대규모 프로젝트에서는 아이콘을 컴포넌트화할 필요가 있다.
그 이유는 크게 두 가지인데…
첫째, 색상만 다른 아이콘 파일이 여러 개 생성되는 문제를 해결해 중복 아이콘을 제거하기 위해서,
둘째, 중복된 아이콘을 제거함으로써 외부에서 아이콘의 stroke, fill 색상, 너비, 높이 등을 쉽게 조정할 수 있게 하기 위함이다.
🤔 그런데 svg 아이콘을 vue 컴포넌트로 관리하면, 성능에 영향을 주지 않을까?
그래서… 이번 시간에는 svg 파일을 이미지로 로드하는 방법, svg 아이콘 컴포넌트 방법,
특정 경로의 svg를 컴포넌트로 로드하는 방법, 총 3가지 방식의 성능을 비교해보았다!
2. 3가지 아이콘 관리 방식과 성능 측정
📍 아이콘 선언 방식 별로 성능을 비교하고자, 총 300개의 아이콘을 로드해서 테스트했다.
빌드 시간과 페이지 로드 성능을 테스트하고 싶다면, 이 레포의 코드 기반으로 테스트 가능하다 :)
1) svg 파일을 이미지로 로드하는 경우
(1) 설명
- 첫 번째는 제일 기본적인 방식으로, img 태그에 svg 파일을 로드하는 형태이다.
- 이 방법의 장점은 svg 아이콘을 추가할 때 svg을 수정할 필요가 없다는 점인데,
- 단점으로는 같은 아이콘 형태이지만 색상만 다른 경우에도 svg 아이콘 파일을 추가해줘야 한다.
<template>
<img src="/svg/icon1.svg" alt="icon" />
</template>
- 그럼 이 방식대로 아이콘을 로드할 경우, 빌드 & 브라우저 성능은 어떨까?
- 빌드 시간 & 빌드파일 크기,
FCP
(First Content Paint),TBT
(Total Block Time), 요청 응답 시간 등을 확인해보자!
(2) 빌드 시간 & 파일 크기 체크
- Vite & Vue 환경에서 300개의 아이콘을 로드해봤는데, 서버 실행 시간은 132ms이었다.
- 빌드했을 때 시간은 725ms가 소요되었고, dist 파일 크기는 552kb였다.
(3) FCP, TBT 체크
- 그 다음으로 Chorme의 [lighthouse]탭에서
FCP
,TBT
을 확인해보았다. FCP
(First Contentful Paint)는 유저가 페이지를 처음 진입했을 때, 페이지 콘텐츠가 렌더링되는 데 걸리는 시간이다.- 이 시간은 1.8초 이하일 때 사용자에게 좋은 경험을 준다.
TBT
(Time Blocking Time)은FCP
이후, 사용자의 클릭이나 입력과 같은 상호작용에 즉시 응답하지 않은 시간이다.- 쉽게 말해, 페이지 로드 중에 자바스크립트 실행이나 다른 작업으로 인해 메인 스레드가 차단되는 시간이다.
TBT
가 길어질수록 사용자는 동작에 대한 상호작용을 즉시 받을수 없는데,TBT
가 길어질수록 사용자 경험은 나빠진다.TBT
의 경우, 모바일 환경 기준으로 200밀리초 미만이어야 한다.
그럼 svg 파일을 로드하는 경우, 두 측정 항목은 얼마가 나올까?
- 데스크탑 기준
FCP
는 0.6초로 양호한 편이었는데, 다만 모바일 기준상 2.8초로 개선이 필요했다. TBT
는 모바일 기준으로 20밀리초로 성능상 양호했다.
측정 기기 | FCP |
TBT |
---|---|---|
데스크탑 | 0.6초 | 0초 |
모바일 | 2.8초 | 20밀리초 |
(4) 서버 응답 시간 체크
- 마지막으로 크롬의 [네트워크] 탭에서 서버 응답 대기 시간, 콘텐츠 다운로드 시간을 확인했다.
- 서버 응답 대기 시간 (Server Response Time, TTFB)는 서버가 요청을 받아 첫 번째 바이트를 클라이언트에 전송하기까지 걸리는 시간이다.
TTFB
가 길어지면 사용자가 페이지를 처음으로 볼 수 있는 시간이 지연돼 사용자 경험에 영향을 준다. - 콘텐츠 다운로드 시간 (Content Download Time)는 서버에서 클라이언트로 데이터를 전송하는 데 걸리는 시간이다. 이 값이 크면 리소스(이미지, 스크립트, 스타일시트) 로드 시간이 늘어나, 전체 페이지 렌더링 속도가 느려진다.
- 하나의 svg 아이콘을 로드할 때, 2가지 측정값의 최소 최대 값은 아래와 같다.
서버 응답 대기 시간 | 콘텐츠 다운로드 시간 | |
---|---|---|
최소 | 0.25밀리초 | 0.076밀리초 |
최대 | 5.03밀리초 | 2.14밀리초 |
- 300개의 아이콘을 로드했을 때,
DOMContentLoaded
는 35밀리초, 로드 시간은 471밀리초가 소요됐다.
2) svg 아이콘 컴포넌트를 사용하는 경우
(1) 설명
- 두 번째는, svg 파일을 vue 컴포넌트로 변환하는 방법이다.
- svg 파일을 vue 컴포넌트로 만드는 방식은 간단한데, vue파일의 template에 svg 코드를 넣고,
width
,height
,color
를 외부에서 컨트롤하도록 선언하면 된다. - 이 방법은 매번 아이콘 컴포넌트를 만들어야 하는 번거로움이 있지만,
- 같은 형태이지만 색상이 다른 아이콘을 하나의 컴포넌트로 컨트롤할 수 있다.
// ArrowDownIcon.vue
<template>
<svg
:width="props.width"
:height="props.height"
style="display: block"
viewBox="0 0 24 24"
>
...
</svg>
</template>
<script setup>
const props = defineProps({
width: {
type: String,
default: "800px",
},
height: {
type: String,
default: "800px",
},
color: {
type: String,
default: "currentColor"
}
});
</script>
<template>
<ArrowDownIcon width="200px" height="150px" color="red" />
<ArrowDownIcon width="300px" height="350px" color="blue" />
</template>
(2) 빌드 시간 & 파일 크기
- 첫 번째 방법과 마찬가지로 Vite & Vue 환경에서 300개의 아이콘을 로드해봤는데, 서버 실행 시간은 164ms로, 앞선 방법보다 약 30ms 더 소요됐다.
- Vite로 빌드했을 때 시간은 9.51s가 소요되었고, dist 파일 크기는 무려… 15.8mb였다;;
(3) FCP, TBT 체크
FCP
,TBT
도 성능이 좋지 않았는데… 데스크탑 기준FCP
는 2.9초로, 모바일 기준 11.9초 였다.- 그리고
TBT
는 모바일 기준으로 230밀리초로 권장하는 200밀리초보다 30밀리초를 초과했다.
측정 기기 | FCP |
TBT |
---|---|---|
데스크탑 | 2.9초 | 40초 |
모바일 | 11.9초 | 230밀리초 |
(4) 서버 응답 시간
- 평균적으로 하나의 아이콘을 로드할 때, 응답 대기, 다운로드 시간의 최소 최대 값은 아래와 같다.
서버 응답 대기 시간 | 콘텐츠 다운로드 시간 | |
---|---|---|
최소 | 85마이크로초 | 91마이크로초 |
최대 | 0.87밀리초 | 1.17밀리초 |
- 300개의 아이콘을 로드했을 때,
DOMContentLoaded
는 38밀리초, 로드 시간은 135밀리초가 소요됐다.
3) 특정 경로의 svg를 컴포넌트에서 로드하는 경우
(1) 설명
- 마지막 방법은, 특정 위치에 있는 svg 파일을 컴포넌트에서 로드하는 방식이다. (예시 보기)
- 사용처에서 아이콘 컴포넌트에 아이콘 이름과 너비, 높이, 색상을 넘기면, fetch API로 svg 아이콘을 불러와서 svg를 재구성한다.
- 두 번째 방법처럼, 아이콘 별로 컴포넌트를 만들지 않지만 너비, 높이, 색상은 외부에서 props로 쉽게 컨트롤 할 수 있다.
- 단, 이 방식을 쓸 때는 이미 로드된 아이콘을 재로드하지 않기 위해 캐시 스토어가 필요하다. (ex.
useSvgCacheStore
)
<template>
<div>
<Icon name="sample" width="50" height="50" color="blue" hover-color="gray" />
<Icon name="sample" width="50" height="50" color="red" hover-color="gray" />
</div>
</template>
<script setup lang="ts">
import { provide } from 'vue';
import Icon from "./components/icons";
import { useSvgCacheStore } from "./composables/useSvgCacheStore";
const svgCacheStore = useSvgCacheStore();
provide("svgCacheStore", svgCacheStore);
</script>
(2) 빌드 시간 & 파일 크기
📍 svg 파일을 커스텀하지 않아도 사용처에서 너비, 높이, 색상을 바꿀 수 있다니…!
너무 유용한데? 그럼, 성능도 좋을까? 빌드 시간과 빌드 파일 크기를 측정해보았다.
- 우선 서버 실행 시간은 168ms로, 두 번째 방법보다 약 3ms 더 소요됐다.
- 빌드 시간은 889ms가 소요되었고,
- dist 파일 크기는 555kb로 svg 파일을 이미지로 로드하는 방식과 유사했다.
(3) FCP, TBT 체크
FCP
는 svg 파일을 이미지로 로드하는 방식과 유사했는데, 데스크탑 기준 0.6초로, 모바일 기준 2.9초 였다.- 그리고
TBT
는 모바일 기준으로 130밀리초로 양호했다.
측정 기기 | FCP |
TBT |
---|---|---|
데스크탑 | 0.6초 | 10밀리초 |
모바일 | 2.9초 | 130밀리초 |
(4) 서버 응답 시간
- 하나의 svg 아이콘을 로드할 때, 응답 대기, 다운로드 시간의 최소 최대 값은 아래와 같다.
서버 응답 대기 시간 | 콘텐츠 다운로드 시간 | |
---|---|---|
최소 | 0.20밀리초 | 95마이크로초 |
최대 | 2밀리초 | 1.57밀리초 |
- 300개의 아이콘을 로드했을 때,
DOMContentLoaded
는 36밀리초, 로드 시간은 46밀리초가 소요됐다.
3. 아이콘 관리 방식 간의 성능 비교
📍 앞서 아이콘을 선언하는 3가지 방식을 알아보았다. 그럼 이 3가지 방식 중에 어떤 방식이 성능이 우수할까?
측정 결과를 표로 비교해보았다.
1) 비교해보기
(1) 빌드 시간 & 파일 크기
(A) svg 파일을 이미지로 로드하는 경우 |
(B) svg 아이콘 컴포넌트를 사용하는 경우 |
(C) 특정 경로의 svg를 컴포넌트에서 로드하는 경우 |
|
---|---|---|---|
빌드 시간 | 725ms | 9.51s | 889ms |
빌드 파일 크기 | 552kb | 15.8mb | 555kb |
- 빌드 시간:
- (A) 방식은 725ms로 빌드 시간이 제일 빨랐다.
- (C) 방식은 889ms로 두 번째로 빠릅니다.
- (B) 방식은 9.51s로 상대적으로 매우 느렸다.
- 빌드 파일 크기:
- (A) 방식은 552kb로 가장 작은 파일 크기를 가진다.
- (C) 방식은 555kb로 (A)와 거의 동일한 크기였다.
- (B) 방식은 15.8mb로 매우 큰 파일 크기를 가졌다!
(2) FCP, TBT
(A) svg 파일을 이미지로 로드하는 경우 |
(B) svg 아이콘 컴포넌트를 사용하는 경우 |
(C) 특정 경로의 svg를 컴포넌트에서 로드하는 경우 |
|
---|---|---|---|
FCP | 데스크탑(0.6초), 모바일(2.8초) | 데스크탑(2.9초), 모바일(11.9초) | 데스크탑(0.6초), 모바일(2.9초) |
TBT | 모바일(20밀리초) | 모바일(230밀리초) | 모바일(130밀리초) |
- FCP (First Contentful Paint):
- 데스크탑:
- (A)와 (C) 방식은 각각 0.6초로 가장 빨랐다.
- (B) 방식은 2.9초로 가장 느렸다.
- 모바일:
- (A) 방식은 2.8초로 가장 빨랐다.
- (C) 방식은 2.9초로, (A)와 비슷한 성능을 보였다.
- (B) 방식은 11.9초로 매우 느렸다.
- 데스크탑:
- TBT (Total Blocking Time):
- (A) 방식은 20밀리초로 가장 적은 TBT를 보였다.
- (C) 방식은 130밀리초로 그 다음을 차지했다.
- (B) 방식은 230밀리초로 가장 많은 TBT를 기록했다.
(3) 서버 응답 시간
(A) svg 파일을 이미지로 로드하는 경우 |
(B) svg 아이콘 컴포넌트를 사용하는 경우 |
(C) 특정 경로의 svg를 컴포넌트에서로드하는 경우 |
|
---|---|---|---|
아이콘별 서버 응답 대기 시간 |
5.03밀리초 ~ 0.25밀리초 | 0.87밀리초 ~ 85마이크로초 | 2밀리초 ~ 0.20밀리초 |
아이콘별 콘텐츠 다운로드 시간 |
2.14밀리초 ~ 0.076밀리초 | 1.17밀리초 ~ 91마이크로초 | 1.57밀리초 ~ 95마이크로초 |
총 DOMContentLoaded 시간 | 35밀리초 | 38밀리초 | 36밀리초 |
총 로드시간 | 471밀리초 | 135밀리초 | 46밀리초 |
- 아이콘별 서버 응답 대기 시간:
- (B) 방식이 0.87밀리초 ~ 85마이크로초로 가장 빨랐다.
- (C) 방식은 2밀리초 ~ 0.20밀리초로 두 번째로 빨랐다.
- (A) 방식은 5.03밀리초 ~ 0.25밀리초로 가장 느렸다.
- 아이콘별 콘텐츠 다운로드 시간:
- (B) 방식이 1.17밀리초 ~ 91마이크로초로 가장 빨랐다.
- (C) 방식은 1.57밀리초 ~ 95마이크로초로 두 번째로 빨랐다.
- (A) 방식은 2.14밀리초 ~ 0.076밀리초로 가장 느렸다.
- 총 DOMContentLoaded 시간:
- 세 가지 방식 모두 큰 차이는 없지만, (A) 방식이 35밀리초로 가장 빨랐다.
- (C) 방식은 36밀리초, (B) 방식은 38밀리초로 비교적 유사했다.
- 총 로드시간:
- (C) 방식이 46밀리초로 가장 짧았다.
- (B) 방식은 135밀리초로 중간 수준이었다.
- (A) 방식은 471밀리초로 가장 길었다.
2) 결론
- 빌드 시간, 서버 응답 시간은 (B) 아이콘 컴포넌트 방식이 제일 성능이 좋지 않았다.
- 그에 비해 (C) 특정 경로의 svg를 컴포넌트에서 로드하는 방식은 전반적인 성능이 좋았다.
(A) svg 파일을 이미지로 로드하는 경우 |
(B) svg 아이콘 컴포넌트를 사용하는 경우 |
(C) 특정 경로의 svg를 컴포넌트에서 로드하는 경우 |
|
---|---|---|---|
빌드 시간 & 빌드 파일 크기 |
빌드 시간과 파일 크기가 가장 유리하다. | 빌드 시간이 길고 파일 크기가 컸다. | 전반적으로 균형 잡힌 성능을 보인다. |
FCP & TBT |
FCP와 TBT도 우수한 성능을 보인다. | FCP와 TBT 성능이 가장 저조했다. | FCP, TBT 성능이 양호했다. |
서버 응답 시간 & 로드 시간 |
서버 응답 및 로드 시간 측면에서는 다소 불리했다. | 서버 응답 시간과 콘텐츠 다운로드 시간이 가장 빨랐다. | 서버 응답 시간 및 로드 시간이 좋았다. |
- 만약 서비스에서 형태는 같지만 색상만 다른 아이콘이 많은 경우,
- 각 svg 아이콘을 컴포넌트화 하기 보다는 특정 경로의 svg를 컴포넌트에서 로드하는 방식을 쓰면 어떨까?
'개발 기술 > 개발 이야기' 카테고리의 다른 글
[JS/CSS] corner smoothing을 구현하는 법(feat. 부드러운 둥근 모서리) (0) | 2024.06.18 |
---|---|
이미지, 배경이미지의 지연 로드 구현 방법(with. Vue) (2) | 2024.06.10 |
이미지, 배경이미지의 지연 로드 구현 방법 (with. intersectionObserver API) (0) | 2024.05.30 |
[React] 아이콘 컴포넌트를 선언하는 3가지 방법 (0) | 2024.04.14 |
[vscode] 컬러 변수 뷰어 만들기(2) - colorvariabletracker (0) | 2024.03.31 |
댓글