최근 프로젝트에서 리액트 앱을 배포하였는데, 인터넷 속도가 느린 환경에서 자바스크립트를 다운로드 받는 시간이 오래 걸린다는 걸 눈치챘다.
눈치챘다는 표현이 어울리는데 왜냐하면 사실 당연한 건데 이제까지 신경쓰지 않았던 부분이기 때문이다. 리액트앱은 매우 큰 자바스크립트 파일 하나로 이루어져있고, 이를 다운로드 받는 것은 당연히 오래 걸리는 일이기 때문이다.
따라서 이 시간 동안 단순한 로딩 화면이라도 띄울수있다면 사용자 경험을 향상 시킬 수 있을 것이라 생각되어 관련된 공부를 하게 되었다.
리액트 앱이 띄워지기까지
사용자가 우리의 앱에 접근하고, 리액트가 사용자 브라우저에 띄워지기까지 다음과 같은 과정을 거친다.
사용자는 주소창으로 우리의 앱에 접근한다.
- 서버는 index.html을 사용자에게 보낸다.
- 사용자의 브라우저는 index.html을 읽고, 필요한 파일들을 파악한 후, 서버로 요청한다.
- 서버는 필요한 파일들을 보낸다. 이때 리액트 번들 파일도 보낸다.
- 다운로드 된 번들 파일을 실행하여 리액트 앱을 띄운다.
이때 우리가 로딩 처리해줄 수 있는 순간은 바로 4번뿐이다. 1 ~ 3번은 우리가 처리할 수 없고, 네트워크과 브라우저에게 맡겨야하는 부분이기 때문이다. 5번에서는 각종 초기화 작업들이 이루어 질텐데, 이미 리액트 앱은 실행된 상태이므로 앱 안에서 로딩처리하면 될 일이다.
자, 그럼 4번을 어떻게 처리해야할까? 이 주제에 관해 얘기하기 전에 먼저 정말로 처리해줘야하는지 알아보자.
다음은 크롬 개발자 도구를 사용하여 보급형 휴대전화에서 필자의 프로젝트의 다운로드 속도를 측정한 것이다.
- yourlist.me (2.16초) : 서버로 접근하고, 응답을 받기까지 걸린 시간
- www.yourlist.me(2.15초) : index.html을 요청하고 받는데까지 걸린 시간
- main.~~~.js(10.47초) : 리액트 js 번들파일
위 결과를 보면 알겠지만, 보급형 휴대전화를 사용하는 사용자는 리액트 앱이 실행되기까지 15초 가량이 걸리게 된다. 만약 그 시간동안 흰화면만 띄워지고 아무런 인터랙션도 없다면, 필자라면 그냥 그 사이트를 나갈 것이다. 이는 유저이탈을 불러일으켜 서비스 운영에 악영향을 끼치게 된다. 따라서 우리는 시간동안 무엇이든 띄워야한다.
JS 다운로드 시간동안 로딩 처리 방법
방법은 간단하다.
먼저 리액트 프로젝트를 만들면 생성되는 public/index.html 파일의 초기 상태는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="ko">
<meta charset="utf-8" />
<link rel="icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
여기서 리액트 앱은 body 안의 “root”라는 id를 가지는 div 안에서 실행되니, 우리는 그 밖에서 무언가를 작업하면 js 파일이 다운로드 받기 전에도 무언가를 띄울 수 있다는 것을 짐작할 수 있다.
따라서 필자는 다음과 같이 index.html을 작성하여 로딩 엘리먼트를 띄웠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="ko">
<meta charset="utf-8" />
<link rel="icon" type="image/png" href="%PUBLIC_URL%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
<!--
index.html에 적용할 css 파일 import
-->
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%/index.css" />
<style>
/* css 파일을 연동하는 시간을 아끼기 위한 내부 스타일시트 선언*/
#loadingElement {
animation: 1s linear infinite rotateLoadingElement;
}
@keyframes rotateLoadingElement {
from {
transform:translate(-50%,-50%) rotate(0deg);
}
to {
transform:translate(-50%,-50%) rotate(360deg);
}
}
</style>
</head>
<body>
<div id="root"></div>
<!--
번들을 다운로드 받는 동안, 아래 loading화면을 띄움
css 파일을 연동하는 시간도 아끼기 위해 inline style도 사용함
번들 다운로드 후, 내부 api 작업을 위한 로딩은 #root 안에서 해결
-->
<div id="loading">
<img id="loadingElement" src="%PUBLIC_URL%/loading.png" style="position:absolute; width:100px; top:50%; left:50%;"/>
</div>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
/* index.css */
#root:not(:empty) ~ #loading {
display: none;
}
#loading {
background-color: #f9f9f9;
width: 100vw;
height: 100vh;
}
- root 엘리먼트 아래에 로딩 엘리먼트를 추가하여 번들 다운로드 중에도 화면에 로딩 엘리먼트를 띄웠다.
- css를 사용하여, root가 비어있을때만 로딩 엘리먼트를 띄우도록 했다. 따라서 리액트 앱이 실행되면 자동으로 로딩 엘리먼트는 사라지게 된다.
- 인라인 스타일과 인터널 스타일시트를 사용하여, css 파일을 다운로드 받는 시간조차 아꼈다.
그 결과가 다음과 같다.
index.html이 다운로드 받아지고, js 번들파일이 다운로드 되기 전까지 로딩 엘리먼트가 제대로 띄워짐을 알 수 있다. (이후 다른 로딩엘리먼트가 띄워지는데, 이는 리액트 앱 내부에서 렌더되는 것이다.)
이로써 유저는 번들 파일이 다운받아지는 시간동안 최소한 작업이 진행되고 있다는 것을 알 수 있어 조금이라도 더 기다리게 할 수 있다.
여기에 단순 로딩엘리먼트만이 아닌, 작업 안내 페이지를 띄운다면 더욱 효과가 클 것이다.