웹/React

[리액트/React] 웹 성능 최적화 해보기(Webp 와 lazyLoading)

P1su 2025. 1. 21. 12:20

각 페이지를 나타날 때 고화질 이미지를 사용한다. 

CSR을 기반으로 하여 첫 로딩이 느린 것에 더하여 고해상도 이미지 때문에 부하가 더 심한 것 같다는 생각이 들었다. 

 

이에 속도를 개선해보고 싶다는 생각이 들었고, 성능 최적화를 진행해보았다. 

 

https://pagespeed.web.dev/?hl=ko

 

PageSpeed Insights

올바른 URL을 입력하세요.

pagespeed.web.dev

PageSpeed 라는 사이트를 사용하여 성능을 측정했다. 

 

55점,,이라고 한다. 

아래로 내려가면 상세한 진단을 확인할 수 있다. 

각 진단 내용을 누르면 해결 방법에 관한 문서들도 제공해준다. 

 

우선 이미지에 대한 최적화를 진행하였다. 

이미지 webp 로 변환하기

webp는 차세대 이미지 형식으로, 구글에서 발표하였으며 다른 이미지 형식들보다 더 나은 압축을 제공한다. 

즉 다른 이미지 형식들보다 파일 크기를 더 작게 가져갈 수 있다. 

 

모든 jpg, png 형식들의 고해상도 이미지를 webp 형식으로 변경해주었고 더하여 각각의 이미지 크기들도 지정해주었다. 

그 결과 성능이 무려 85까지 증가했다..

 

실제 개발에 사용했던 이미지들이고, 이를 webp로 변환한 파일들이다. 

파일 크기가 무려 30배에서 40배 가까이 차이가 난다. 

이미지 로딩이 무거웠던 이유가 있었다. 

lazyLoading 설정하기

지연 로딩으로 번들을 여러 청크들로 나누어 필요한 파일들만 불러와서 로딩할 수 있도록 도와주는 기능이다. 

초기 코드를 빌드해보았을 때 index.js 의 크기는 위와 같았다. 

또한 별도의 js 와 css 파일들은 보이지 않고, index.js 와 css 파일 하나만을 확인할 수 있다. 

또 위와 같은 문구를 확인할 수 있는데, 파일의 크기가 커서 코드 스플리팅을 권장하는 문구이다. 

 

페이지별로 코드 스플리팅(분할)을 적용하여 페이지에 필요한 정보들만 불러올 수 있도록 한다. 

 

React.lazy 함수를 이용하여 함수를 동적으로 import 해온다. 

//기존 함수 import
import Main from '../pages/Main/Main';

//lazy 함수 적용
export const Main = React.lazy(() => import('../pages/Main/Main'));

 

코드가 로드되는 동안 보여줄 로딩 컴포넌트, Suspense 설정도 해주어야 한다. 

{
  path: '/',
  element: (
    <Suspense fallback={<div>Loading...</div>}>
      <Main />
    </Suspense>
  ), 
},

위와 같이 lazyLoading 을 통하여 불러올 컴포넌트에 대해 Suspense 설정을 해준다. 

 

function App() {
  const queryClient = new QueryClient();
  return (
    <QueryClientProvider client={queryClient}>
      <Suspense fallback={<div>Loading...</div>}>
        <RouterProvider router={router} future={{ v7_startTransition: true }} />
      </Suspense>
    </QueryClientProvider>
  )
}

나는 App.jsx 에서 한번에 감싸주었다.

 

import 문이 복잡해지는 것을 생각하여 lazy.js 라는 파일을 만들어 동적으로 불러오는 코드를 따로 작성하였다. 

import React from 'react';

export const Main = React.lazy(() => import('../pages/Main/Main'));
export const Materials = React.lazy(() =>
  import('../pages/Materials/Materials')
);
export const Login = React.lazy(() => import('../pages/Login/Login'));
export const MaterialDetail = React.lazy(() =>
  import('../pages/Materials/MaterialDetail/MaterialDetail')
);
.
.
.

 

이후 동일하게 작성해준다. 

import { createBrowserRouter } from 'react-router-dom';
import Layout from '../layouts/Layout';

import {
  Main,
  Materials,
  Login,
  MaterialDetail,
  Reservation,
  ReservationDetail,
  Event,
  EventDetail,
  Portfolio,
  PortfolioDetail,
  ReservationBoard,
  EventCreate,
  PortfolioCreate,
} from './lazy';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        path: '/',
        element: <Main />,
      },
      {
        path: '/materials',
        element: <Materials />,
      },
      {
        path: '/material/:company/:name',
        element: <MaterialDetail />,
      },

 

build를 해보면

여러개의 파일들이 나누어 생성되었으며 이에 따라 index.js 의 크기 역시 줄어들었다. 

또한 빌드 시간 역시 줄어들었는데, 청크 파일로 분리하는 과정에서 불필요한 코드나 중복이 제거된 것이 요인인 것 같다. 

코드 스플리팅 이전 내역을 보면 메인페이지에서 사용되지 않는 컴포넌트들과 함수들이 같이 로드되는 것을 확인할 수 있다. 

lazyLoading 을 통한 코드 스플리팅 이후 메인 페이지에서 필요한 파일들만 로드되는 것을 확인할 수 있다. 

그리고 최대 1000밀리초까지 걸리던 로딩 시간이 약 500밀리초 후반까지 줄어든 모습도 확인할 수 있다. 

 

맨 윗줄에 로드되는 검은색 줄은 구글 캘린더 api 연동 관련 시간이어서 실제 파일 로드와는 큰 연관이 없어 보인다. 

 

내가 사용하는 기술들에 대해 조금은 익숙해져서 조금은 다른 작업을 해보고 싶었고, 그렇게 해서 시도해 본 성능 최적화였다. 밀리초 단위로 화면이 로드되기 때문에 화면상으로는 엄청난 속도 변화를 느끼지는 못했지만, 수치적으로는 최적화가 나름? 성공한 것 같다..! 

아마 프로젝트 규모가 커질 수록 퍼포먼스가 더 커지지 않을까 라는 생각이 들었다. 

또 다른 프론트 개발자분께서 번들 크기의 최소화를 계속해서 강조했었다. 

그 당시에는 기능 구현에만 급급하여 크게 와닿지 못하였는데, 이제서야 그 이유를 알 것 같다..

728x90