본문 바로가기

React

[리액트] 왕초보 개발자의 웹성능 최적화 수기

실전프로젝트가 끝났다. 이번에 우리 조가 했던 서비스는 생각보다 잔잔한 기능들이 많았고 디자인도 여러번 바뀐 탓에 css 작업이 많아 여러모로 시간이 부족했다.때문에 리팩토링 및 성능최적화를 할 시간이 부족했다. 하지만 빠른 웹사이트를 만들어내는 것은 프론트엔드의 중요한 덕목이라고 생각했고 이것을 신경쓰는 개발자라는 것을 보여주고 싶어, 발표전까지 어떻게든 성능최적화 해내기 위해서 노력했다. 성능을 최적화하기 위해서 이것저것 구글을 검색해 정보를 얻어 내가 만든 사이트에 최적화를 적용했다. 이 과정을 공유해보고자 한다. 


성능을 최적화하기에 앞서, 성능이란 무슨 말인가? 성능이 좋다는 말은 무엇인가? 

웹 성능이란 사용자에게 얼마나 빠르게 웹 콘텐츠를 전달할 수 있는가를 말하는 것이다. 즉 주소창에 도메인 주소를 입력하여 사이트에 접속하고 웹페이지가 로딩되어 내용을 볼 수 있을 때까지 걸리는 시간을 말한다. 

웹성능이 좋다는 것은 말 그대로 내용이 빠르게 전달된다는 것이다. 

 

나의 웹성능에 영향을 미치는 요소는 무엇인가? 

웹성능을 향상시키기 위해선, 먼저 지금 내가 만든 웹성능 저하에 영향을 미치는 요인이 무엇이 있는지 분석을 해야한다. 분석이 선행되면, 무엇을 고쳐야할 지 눈에 보인다. 우선 이번에 내가 진행했던 분석 방법은 2가지이다. 

 

 

1)개발자 도구의 네트워크탭에서 확인한다. 

개발자 도구에 들어가면 네트워크 탭이 있다. 여기에 들어가면, 아래와 같이 disable cache와 fast 3G라는 탭을 선택할 수 있다. disabla cache를 선택하면, 브라우저에서 하는 캐싱 행위를 막아주기 때문에 언제나 처음 로딩하는것처럼 확인이 가능하다. fast 3G라는 탭을 선택하면, 웹사이트의 속도를 적나라하게 확인이 가능하다. 

네트워크 탭의 Disable cache와 Fast 3G

 

아래의 사진은 나의 프로젝트에 성능 최적화를 적용하기 전의 사진이다. 아래에 나와있는 도표들은 어떤 요인들로 인해서 성능이 저하되고 있는지 설명해주고 있다. 가운데 있는 부분에서는 개별적인 요소들에 있어서 몇초의 시간이 걸렸는지를 보여준다. 이런부분들을 잘 확인하고 하나하나 개선해나가는 것이 필요하다. 최종적으로 맨 밑에 있는 탭에서는 전체 로딩속도가 얼마나 걸렸는지를 보여주고 있다. 

네트워크 탭

맨 밑에 보이는 탭에서 DOMContentLoaded 는 DOM Tree 구조를 그리는데 걸리는 시간을 말하며, Load는 DOM Tree 구조를 포함, 이미지까지 화면에 로드되는 시간을 말한다.  아래 사진을 보면, 전체 이미지까지 화면에 로드 되는시간이 21.36초... 이대로는 안된다..

렌더링 최종 속도

 

 

2)번들 분석툴을 사용한다. 

검색해서 찾아보니, 웹성능에 영향을 미치는 요인 중에는 당장에 필요하지 않은데 한번에 많은 컴포넌트를 불러오는 것도 있다는 사실을 발견했다. 아래는 이를 확인해볼 수 있는 분석 라이브러리이다.

 

<설치 명령어>

npm install -D cra-bundle-analyzer

<실행 명령어>

npx cra-bundle-analyzer

 

위의 명령어로 설치하고 실행을 하면, 아래와 같은 화면이 나온다. 

어떤 파일, 컴포넌트가 많은 용량을 차지하고 있는지, 얼마나 뭉쳐있는지를 보여주고 있다. 뭉쳐있는것이 많으면, 당장 필요하지 않는데도 컴포넌트를 불러오기 때문에 초기렌더링 속도에 많은 영향을 미친다고 한다. 개선이 필요하다. 

 

지금까지 내가 분석했던 2가지 방법을 소개했다. 이제 분석한 내용을 토대로 하나씩 개선해나갔던 과정을 소개해보겠다. 

 


 

1.컴포넌트 레이지 로딩 

먼저 번들 최적화부터 진행했다. 나는 APP.js 컴포넌트에서 라우터를 활용해 페이지들을 불러오고 있었다. 프로젝트에는 많은 페이지들이 있었는데, 초기 렌더링시 이 많은 페이지들을 한꺼번에 불러오는 문제점이 있었다. 이를 고치기 위해서는 레이지 로딩을 적용해야했다. 아래는 참고했던 리액트 공식문서다.  

https://ko.reactjs.org/docs/code-splitting.html

 

아래와 같은 방식으로 컴포넌트를 불러올 때, lazy로딩을 적용한다. lazy로딩을 적용할 때는, 해당 컴포넌트를 suspence로 감싸줘야한다. 

suspense를 사용하면, 렌더링이 되기까지 기다릴 수 있다. 

//main
const Home = lazy(() => import("../pages/Home"));
const Login = lazy(() => import("../pages/Login"))
const LoginCheck = lazy(() => import("../pages/LoginCheck"))
    <Suspense fallback={Loading}>
          <Switch location={location}>
          <Route path="/" exact component={Home} />
          <Route path="/login" exact component={Login} />
          <Route path="/logincheck" component={LoginCheck} />
          </Switch>
    </Suspense>

suspense의 fallback에 특정 컴포넌트를 넣으면 그것을 렌더링이 되는동안 보여준다. 

 

이렇게 컴포넌트들을 레이지로딩 적용한 후 다시 번들 분석툴을 실행하니 다음과 같은 화면이 나타났다. 

 

첫번째 사진과 비교했을 때, 뭔가 더 세부적으로 나눠진 것을 확인할 수 있다. 아직까지 개선의 여지가 많겠지만, 우선 여기까지 적용이 가능했다.. 결과 초기 랜더링시간을 3초가량 줄일 수 있었다. 

 

2.무거운 라이브러리 확인

분석툴을 확인하니, 무거운 라이브러리가 무엇인지, 어떤 파일이 무거운지를 알 수 있었다. 해당 내용들을 지워나갔다. 

로티와 로다시 라이브러리가 상당히 크다. 그래도 나는 로티를 포기할 수 없었다. 로티를 넣음으로써, 화면의 퀄리티가 많이 달라졌기 때문이다... 이건 꼭 넣고 싶었다. 그러면 어떻게 하냐, 이것을 줄일 수 있는 방법을 찾아야했다. 검색을 조금만 해보니, 로티 라이브러리가 가벼운 버전이 있었다. 그래서 기존에 존재하던 lottie-web 버전을 삭제하고, lottie-web-light 버전을 설치했다. 적지 않은 용량을 감소시킬 수 있었다. 

그리고 lodash는 충분히 대체 가능한 라이브러리였다. 자바스크립트 함수를 편하게 사용할 수 있게 도와주지만, 이것 또한 직접 구현이 가능하기 때문이다. 그래서 lodash를 삭제하고, 직접 함수를 구현하는 방식으로 바꾸었다. 아래의 사이트에서는 라이브러리 크기를 확인할 때, 사용한 사이트이다. 

https://bundlephobia.com/

 

다음에, 이것으로 확인했을 때 또 발견된 다른 문제는 로티파일 하나하나가 용량이 굉장히 크다는 것이었다. 이 문제를 해결하기 위해서 약 2-3시간 가량 검색을 했지만, 마땅한 답을 찾지 못했다. gzip을 활용하면 로티 파일을 압축할 수 있다는 글을 발견했으나, 아직까지 gzip을 사용하는 방법을 터득하지 못했다. 이건 추후에 계속 알아보아야 할 과제이다. 

 

 

3.웹폰트 삭제 

별 생각없이 구글 폰트를 아래와 같이 import 해오는 방식으로 사용하고 있는데, 이게 또 초기 렌더링 시간을 3초나 잡아먹고 있었다. 그래서 어떻게 하냐. 이런 방식으로 폰트를 불러오는 것을 삭제하고, 폰트를 파일 자체에 설치한다. 그런데 이때 생기는 문제는 폰트 파일자체가 크다는 것이다. 그래서 경량화된 폰트를 찾고, 필요한 부분만 폰트를 설치하는 과정이 필요하다. 이렇게하면 폰트로 인해서 랜더링 속도가 느려지는 것을 방지할 수 있다. 

 

 

4.이미지 파일 압축 

일단 기본적으로 우리 프로젝트에서 사용하고 있는 이미지들이 많았다. 유저가 올린 게시물에서 가져오는 이미지 뿐만 아니라, 디자인을 위해서 필요한 이미지들도 있었다. 기본적으로 가지고 있는 이미지들 같은 경우에는 우리가 직접 파일을 압축할 수 있었다. 해당 파일들을 모두 압축해서 가져오도록 했다. 여기서도 몇초 가량의 시간을 줄일 수 있었다. 

또한 디자인을 위해서 기본적으로 사용하는 이미지 뿐만 아니라, 유저가 직접 올리는 이미지 파일도 압축을 진행할 필요가 있었다. 이것을 위해서 이미지압축 라이브러리를 사용했다. 유저가 이미지를 올릴 때마다 이미지 파일을 압축했고, 이것을 서버로 전달했다. 

 

 

5.이미지 레이지 로딩 적용 

사실 가장 다이나믹하게 시간을 세이브해준 방법이었다. 초기 랜더링이 아직 화면에 보이지도 않는 이미지들을 불러오느라 초기 랜더링 시간이 늘어난다. 그러니까, 화면에 나타난 순간에만 이미지를 불러오면 되는 것이다. 이런 개념이 이미지 레이지 로딩이다. 

나는 이것을 적용하기 위해서 인터섹션 옵저버를 활용했다. 이미지에 data-형식을 활용해 data-src라는 값 안에 이미지 url을 넣어두었다. 화면에 나타나면, 해당 src를 이미지 src에 할당해주는 방식으로 진행했다. 아래는 내가 적용했던 코드다. 사실 아직 placeholder이미지는 적용 못한 상태이다. 추후에 다시 작업할 예정이다. 

 

//이미지를 선택할 ref 선언  
const observeImage = useRef(null)


//화면에 나타나면 실행할 함수
const showImage = async([entry], observer) => {
    if (!entry.isIntersecting) {
      return
    }
    const imageUrl = [entry][0].target.dataset.src 
    observeImage.current.src = imageUrl
    observer.unobserve(entry.target) // 함수가 실행될 때, 관찰을 끝내기.
}


//인스턴스 생성
  useEffect(() => {
      const observer = new IntersectionObserver(showImage, {threshold: 0.1}); //메인이미지 관찰
      observer.observe(observeImage.current)
    return () => {
      observer.disconnect();}
    },[])
    
 
//이미지 컴포넌트
  	<Image
     alt="Feed_img"
     data-src={image}
     ref={observeImage}
     onClick={() => {
      goToReviewDetail();
     }}
    />

 


 

위와 같은 과정을 거쳐서 정말 다이나믹하게 시간을 줄일 수 있었다. 아래 사진은 적용한 결과이다. 

21초에서 6초까지 시간을 줄일 수 있었다. 아직까지 개선해야 할 부분이 많고, 공부해야 할 내용도 많지만, 이렇게 숫자로 성과가 나타나니 재미가 있었다.

 

 

 

겨우 이제 최적화를 위한 공부를 시작한 것이니 모르는게 많다.

하지만 계속 알아가고 공부해서 성능 좋은 웹사이트를 만들어내는 프론트엔드 개발자가 되고 싶다. 

아래에는 더 공부하고 고민해보아야 할 내용 목록이다. 

 

성능 개선을 위해  발견한,  더 적용하고 공부해야 할 과제 : 

  • 함수 코드 스플리팅 
  • 서버사이드 렌더링이란? 
  • 캐시설정 
  • get 요청을 줄이기