[React] Lighthouse로 성능측정하고 성능 개선하기

이은지·2024년 11월 27일
0

Capstone Design

목록 보기
1/2

캡스톤 디자인 프로젝트에서 Lighthouse를 이용해 웹사이트 성능 측정을 진행하고, 성능 최적화를 수행한 과정을 공유하려 한다. 초기 성능은 다음과 같다. 성능 측면에서 FCP와 LCP가 3.6초로 개선이 필요했으며, 콘텐츠가 포함된 최대 페인트 요소를 확인해보니 렌더링 지연이 96%로 압도적으로 비중이 높았다 이를 해결하기 위해 먼저 폰트 최적화, 이미지 최적화를 수행했다.

1️⃣ 폰트 최적화

기존에는 Google Fonts를 외부에서 불러와 렌더링 지연이 발생했다. 이를 개선하기 위해 Google Fonts를 로컬 폰트 woff2형식으로 다운로드하고, 프로젝트 내부에서 직접 호출하도록 수정하였다. 또한 font-display: swap 속성을 적용하여 텍스트가 즉시 렌더링되도록 변경하였다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link href="/src/index.css" rel="stylesheet" />
    <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <link rel="manifest" href="/manifest.json" />
    <title>Sound Palette</title>
    <style>
      @font-face {
        font-family: "Fira Sans Condensed";
        font-style: normal;
        font-weight: 400;
        font-display: swap;
        src: url("src/assets/fonts/wEOhEADFm8hSaQTFG18FErVhsC9x-tarUfbtrelWfx4.woff2")
          format("woff2");
      }
      @font-face {
        font-family: "Fira Sans Condensed";
        font-style: normal;
        font-weight: 500;
        font-display: swap;
        src: url("src/assets/fonts/wEOsEADFm8hSaQTFG18FErVhsC9x-tarWQXOuMR0cjRYhY8.woff2")
          format("woff2");
      }
      @font-face {
        font-family: "Fira Sans Condensed";
        font-style: normal;
        font-weight: 600;
        font-display: swap;
        src: url("src/assets/fonts/wEOsEADFm8hSaQTFG18FErVhsC9x-tarWSnJuMR0cjRYhY8.woff2")
          format("woff2");
      }
      @font-face {
        font-family: "Fira Sans Condensed";
        font-style: normal;
        font-weight: 300;
        font-display: swap;
        src: url("src/assets/fonts/wEOsEADFm8hSaQTFG18FErVhsC9x-tarWV3PuMR0cjRYhY8.woff2")
          format("woff2");
      }
    </style>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script type="module" src="/src/index.tsx"></script>
  </body>
</html>

결과

  • 성능 점수가 1점 높아졌지만, 여전히 렌더링 지연이 발생했다.

2️⃣ 이미지 최적화

사진 파일 크기를 줄여 로드 시간 줄이기 위해 기존 ".png" 파일들을 ".webp"로 변경해서 성능개선을 시도하려고 했다.

PNG, WEBP의 차이점은 무엇일까?

  • PNG (Portable Network Graphics)
    • 손실 없는 압축. 원본 이미지의 품질을 그대로 유지하면서 파일 크기를 줄인다.
    • 투명도 지원, 이미지의 특정 부분을 완전히 투명하게 만들 수 있다.
    • 주로 로고, 텍스트, 아이콘 같은 고해상도 이미지에 적합하다.
  • WEBP
    • 구글이 개발한 오픈소스 이미지 포맷으로, 손실 압축과 손실 없는 압축 모두 지원한다.
    • PNG보다 더 효율적인 압축을 제공하여, 비슷한 품질의 이미지를 더 작은 파일 크기로 만들 수 있다 .
    • 또한 투명도를 지원하며, 애니메이션 이미지 저장도 가능하다.
    • 웹에서 다양한 유형의 이미지를 효율적으로 사용하고자 할 때 적합하다.

결과

다음과 같이 이미지 크기가 크게 절감되는 효과를 얻을 수 있었으나, 성능 점수에는 큰 영향을 미치지 못했다.

3️⃣ 컴포넌트 지연 로딩

초기 로딩 시 네트워크 탭을 확인한 결과, App.tsx에 정의된 모든 라우터의 페이지 컴포넌트 리소스가 한 번에 로드되는 문제가 발견되었다. 이것이 렌더링 지연에 큰 영향을 미치는 것으로 파악되어 이를 해결하기 위해 Lazy-Loading(지연 로딩) 방식을 도입하였다.

lazy loading: 지연로딩

  • 애플리케이션을 이루는 코드들 중 특정 코드를 필요할 때만 로딩하는 테크닉
  • 로딩을 바로 하지 않고 지연시켰다가 나중에 로딩하게 해 준다는 뜻이다.
  • 사용자가 사이트에 접속했을 때 보이지 않는 것까지 모두 로드해 오는 것이 아니라 보이는 페이지만 로드한 후다른 페이지에 접속했을 때 그곳의 데이터를 로드해오는 작업을 해주는 것이다.

React.lazy를 사용하는 이유

React.lazy를 사용하지 않을 경우 사용자가 첫 페이지를 로드하는 즉시 App.tsx에 import된 컴포넌트 파일들이 빌드 시 하나의 커다란 파일에 병합되어 대규모의 단일 JS번들이 사용자에게 전송이 된다. 만약 수십,수백개의 라우트와 컴포넌트가 있다면 모든 파일(코드)을 불러오는게 문제가 될 수도 있으며, 첫 페이지의 로딩속도도 느려질것이다.(CSR의 단점)

→ React.lazy() 메서드 요소들은 손쉽게 개별 JS 청크로 분리하는 기본 방법을 제공한다.

React.lazy 적용 방법

해당 컴포넌트 코드가 처음 렌더링될 때까지 로드하는 것을 연기하려면 import를 다음과 같이 대체한다. lazy 컴포넌트는 반드시 컴포넌트 외부에서 선언해야 한다.

React.lazy()는 import() 구문을 반환하는콜백 함수를 인자로 받는다.

동적으로 불러오는 컴포넌트 파일에는 반드시 지켜줘야 하는 두 가지 규칙이 있다.

  1. React 컴포넌트를 포함해야 한다.

  2. default export를 가진 컴포넌트여야 한다.

const MainPage = lazy(() => import("./pages/Main"));
const ServiceSelectionPage = lazy(
  () => import("./pages/Create/ServiceSelection")
);

function App() {
	...
}

이제 요청에 따라 컴포넌트 코드가 로드되므로 로드하는 동안 표시할 항목도 지정해야 하는데, lazy 컴포넌트 또는 부모 컴포넌트 중 하나를 바운더리로 감싸서 이 작업을 수행할 수 있다.

const router = createBrowserRouter([
    {
      path: "/",
      element: <RootLayout />,
      children: {
       ...
   
        {
          path: "create",
          element: <PrivateRoute />,
          children: [
            {
              path: "service-selection",
              element: (
                <Suspense fallback={<div>Loading...</div>}>
                  <ServiceSelectionPage />
                </Suspense>
              ),
            },
            {
              path: "file-upload",
              element: (
                <Suspense fallback={<div>Loading...</div>}>
                  <FileUploadPage />
                </Suspense>
              ),
            },
            {
              path: "check-lyric/:itemId",
              element: (
                <Suspense fallback={<div>Loading...</div>}>
                  <CheckLyricPage />
                </Suspense>
              ),
            },
            {
              path: "analysis-result/:itemId",
              element: (
                <Suspense fallback={<div>Loading...</div>}>
                  <AnalysisResultPage />
                </Suspense>
              ),
            },
            {
              path: "generate-prompt/:itemId",
              element: (
                <Suspense fallback={<div>Loading...</div>}>
                  <GeneratePromptPage />
                </Suspense>
              ),
            },
            {
              path: "view-result/:itemId",
              element: (
                <Suspense fallback={<div>Loading...</div>}>
                  <ViewResultPage />
                </Suspense>
              ),
            },
          ],
        },
      ],
    },
  ]);

결과

  • 각 페이지를 요청 시 로드하도록 분리하여 초기 로딩 속도를 개선
  • 네트워크 탭 확인 결과, 불필요한 리소스 로딩이 사라짐

🎉 성능 개선 결과

  • FCP/LCP: 3.6초 -> 1.1초
  • Speed Index: 3.6초 -> 1.1초
  • 성능 점수: 61점 -> 98점

profile
소통하는 개발자가 꿈입니다!

0개의 댓글