[React] Lighthouse로 측정한 성능을 개선해보자

래림·2025년 2월 21일

DOKBARO

목록 보기
4/4
post-thumbnail



들어가기 앞서

이 포스팅은 DOKBARO를 개발하면서 경험한 것을 기반으로 제작하였습니다.

DOKBARO란 ?

자기계발과 성장을 위해 독서와 스터디를 활용하는 개발자들을 위한 퀴즈 학습 플랫폼, DOKBARO입니다.

개발 서적을 즐겨 읽지만, 매번 내용을 제대로 이해했는지 확인하기 어렵지 않으셨나요? 혹은 이해 부족으로 인해 독서 스터디가 소수만 적극적으로 참여하는 형태로 변질되는 경험을 하셨을지도 모릅니다.

그래서, DOKBARO는

📚 퀴즈 출제 및 풀이 기능으로 도서 내용을 재미있고 효과적으로 이해하도록 도와드려요.

💡 스터디 리포트 기능으로 스터디원들이 책에 대해 자유롭게 의견을 나누고, 서로의 학습 현황을 확인할 수 있어요.

DOKBARO와 함께라면 도서 이해도를 높이고, 스터디 활동을 보다 풍성하고 활발하게 만들어 이상적인 독서 환경을 경험하실 수 있습니다. ✌️

현재 베타 오픈중이니 아래 링크를 통해 이용해보실 수 있어요!
https://dokbaro.com




배경

총 5번의 유저 사용성 테스트를 진행하면서 받았던 피드백과, 팀원들의 피드백이 겹치는 부분이 있었다. 첫 화면 로딩이 오래걸린다는 피드백이었다. 랜딩페이지의 이미지를 압축하는 시도를 해보았으나, 여전히 느렸다. 그래서 추가적인 성능개선을 시도해보기로 했다.



성능 측정을 해보자

성능 측정은 Lighthouse로 진행했다. 크롬에서 데스크톱 환경으로 총 10번을 진행했고, 결과로 평균 성능 63.8이 나왔다.
(66, 62, 61, 67, 61, 65, 65, 66, 63, 62)...

FCP: 현재 1.9s → 목표: 0 ~ 1.8s

LCP: 현재 6.0s → 목표 : 0 ~ 2.5s

Speed index: 현재 1.9 → 목표: 0 ~ 3.4s

목표는 chrome for developers에서 점수를 해석하는 방법을 기준으로 "빠름"에 해당하는 구간으로 지정했다.



First Contentful Paint

어떻게 할까 고민하다가, 진단탭에 있는 목록을 하나씩 수정해보기로 했다.


렌더링 차단 리소스 제거하기

내역을 보니 현재 사용하지 않고있는 cdn 2개가 보였다. 1개는 폰트, 나머지 하나는 소셜로그인 관련이었다. 바로 제거하였다.



텍스트 압축 사용

vite 환경에서 텍스트압축을 어떻게 하는지 조사해보았다. 거의 vite-plugin-compression2를 사용하는것 같아 DOKBARO에도 적용했다. 유저테스트를 통해 확인했을때 브라우저로 사파리를 사용하는 사람이 꽤 많았고, 압축률이 더 좋은 br(Brotli)보단 호환성이 좋은 gzip알고리즘을 적용했다.

import { InlineConfig, UserConfig, defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import mkcert from "vite-plugin-mkcert";
import { compression } from "vite-plugin-compression2";

interface VitestConfigExport extends UserConfig {
  test: InlineConfig;
}
// https://vitejs.dev/config/
export default defineConfig({
  server: { https: true },
  plugins: [
    react(),
    mkcert(),
    compression({
      algorithm: "gzip",
    }),
  ],
  resolve: {
    alias: [{ find: "@", replacement: "/src" }],
  },
  test: {
    globals: true,
    environment: "jsdom",
    setupFiles: "./src/test/setup.ts",
  },

} as VitestConfigExport);



압축 결과 아래와 같이 성능이 개선되었다.

텍스트 압축만 해주었는데도 벌써 성능이 71으로 올랐다.



조금 더 진행해 보자..

아직 진단탭에 남은 목록들이 많아서 더 진행해 보기로 했다.



lazyLoading을 통해 코드를 분할해서 로드

lazy를 import하고 router에 정의된 모든 컴포넌트들을 lazy를 사용해 import하였다. 이렇게 하면 필요한 순간에 해당 컴포넌트를 불러오기 때문에 초기 번들 크기를 줄일 수 있다.

router.tsx


import { createBrowserRouter, Navigate } from "react-router-dom";
import ROUTES from "@/data/routes";
import { lazy } from "react";

const BaseLayout = lazy(
 () => import("@/components/layout/BaseLayout/BaseLayout.tsx"),
);
 ... 생략..
 
 const router = createBrowserRouter([
   {
   path: ROUTES.ROOT,
   element: <BaseLayout />,
   ... 생략..
 }
]);

App.tsx
App에서는 fallback을 지정해줘야 하는데, 스켈레톤 UI를 넣고 싶었으나 우선순위에서 밀려 디자이너와 협의끝에 일단 빈 화면으로 두기로 했다..

import { Suspense } from "react";

function App() {
 return (
   <QueryClientProvider client={queryClient}>
     <Suspense fallback={<div></div>}>
       <RouterProvider router={router} />
     </Suspense>
     <ToastPortal />
   </QueryClientProvider>
 );
}
export default App;


manual chunck 분리

코드 스플리팅 키워드로 검색을 하다가 manual chunck 분리를 해주면 성능 최적화 효과를 얻을 수 있다하여 시도해 보았다. 기본적으로 Vite는 자동으로 코드 스플리팅을 지원하지만, 수동으로 청크로 분리하는 작업을 추가적으로 해주면 번들 크기를 줄일 수 있고, 브라우저가 필요한 청크만 다운로드하므로 초기 로딩 속도가 빨라질 수 있다고 한다.

브라우저가 캐싱을 진행할 때 앱 코드가 변경되어도 수동으로 분리한 청크는 변경되지 않아 캐싱을 효율적으로 할 수 있고, 브라우저가 여러개의 청크를 동시에 다운로드 (병렬처리)할 수 있어 로딩속도가 개선되는 효과를 얻을 수 있다한다.

사실 머리로는 알겠는데 경험상 아직 부족한 부분이라 더 적지는 않겠다.

아래의 코드와같이 react, reactRouter, animations처럼 역할별로 그룹을 나누었는데 이렇게 하면 애니메이션을 사용하지 않는 페이지에서는 animations 청크가 로드되지 않으므로 불필요한 다운로드를 줄일 수 있는 이점이 있다고한다.

import { InlineConfig, UserConfig, defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import mkcert from "vite-plugin-mkcert";
import { compression } from "vite-plugin-compression2";

interface VitestConfigExport extends UserConfig {
  test: InlineConfig;
}
// https://vitejs.dev/config/
export default defineConfig({
  server: { https: true },
  plugins: [
    react(),
    mkcert(),
    compression({
      algorithm: "gzip",
    }),
  ],
  ...생략...
  
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          react: ["react", "react-dom", "jotai", "@tanstack/react-query"],
          reactRouter: ["react-router-dom"],
          animations: ["lottie-react", "lottie-web"],
        },
      },
    },
  },
} as VitestConfigExport);



아니 뭘 이렇게까지 좋아져

처음 테스트를 했던 환경과 같은 환경에서 테스트를 10번 진행 한 결과 평균 93.4가 나왔다. 처음 63.8에서 93.4로 성능 점수가 46.39% 개선되었다.

(수행 기록: 93, 94, 93, 93, 94, 92, 93, 95, 94, 93)





결국 코드 스플리팅을 통해 성능이 개선된건가.

성능개선을 어떻게 할까 조사하다가, 거의 공통적으로 코드 스플리팅이라는 개념을 이야기 하고있는걸 알게되었다.

코드 스플리팅이란?

코드분할은 코드를 번들된 코드 혹은 컴포넌트로 분리하는 것입니다. 이렇게하면 필요에 따라 특정한 컴포넌트만 로딩하거나, 병렬로 로딩할 수 있습니다. 출처

결국 성능 최적화를 목적으로 코드를 더 작은 사이즈로 스플리팅 한다는 뜻이다.

지금까지 진행한 스플리팅

  • 텍스트 압축
  • React Router와 함께 사용하여 페이지 단위로 코드 스플리팅
  • lazy Loading(Dynamic Import)
  • manual chunck




참고
텍스트 압축관련
성능최적화
vite-manual-chunck 옵션
번들사이즈 줄이기

2개의 댓글

comment-user-thumbnail
2025년 2월 21일

글이 잘읽혀요~! 유익한 글 감사합니다!!

답글 달기
comment-user-thumbnail
2025년 2월 24일

멋져요!! 최고!!

답글 달기