Vite 최적화를 통한 사용성 개선!

김인태·2024년 10월 29일
0

😎 계기

프론트엔드 개발자는 서버와의 액션을 잘 처리하는 것, UI/UX 고려등의 여러가지 고려해야되는 부분들이 많지만, 중요한 것 중 하나! 얼마나 최적화를 잘했는지가 중요한 요소중의 하나라고 생각합니다

그래서 이번에 크롬 브라우저의 lighthouse 검사를 통해서 최적화를 진행해보려고 합니다.

그렇다면 어떻게 개선 방향을 잡아볼까요?

  1. 렌더링 최적화
  2. 서버 데이터 요청 최적화

이 두 가지의 큰 갈래를 잡아 진행해보도록 하겠습니다.

목표는 모든 페이지의 lighthouse 95점 이상!

이제 달려보도록 하죠?

음.. 생각보다는 높지도, 낮지도 않은데요? 일단 성능부터 고쳐봅시다!

성능

이렇게나 많은 것들이 있군요..일단 하나씩 지워봅시다!

  • 텍스트 압축사용
    • 네트워크 사용 총량을 최소화하려면 텍스트 기반 리소스를(gzip, deflate, broil)하여 제공해야 한다네요?

    • Vite에서는 어떤 방법으로 해결해야할까요?

      import { defineConfig } from "vite";
      import react from "@vitejs/plugin-react";
      import path from "path";
      import svgr from "vite-plugin-svgr";
      import viteCompression from "vite-plugin-compression";
      
      // https://vitejs.dev/config/ß
      export default defineConfig({
        base: "/",
        plugins: [
          react(),
          viteCompression({
            algorithm: "gzip",
            ext: "gz",
          }),
          svgr({
            svgrOptions: {
              icon: true,
            },
          }),
        ],
        resolve: {
          alias: {
            "@": path.resolve(__dirname, "./src"),
            "@components": path.resolve(__dirname, "./src/components"),
          },
        },
      });
      

아주 super easy 합니다. vite-plugin-compression을 설치하고, vite.config.ts 에 설정해주면 끝!!

이를 통해 vite 환경에서 빌드 시 생성되는 assets 파일들을 압축해서 클라이언트 전송 시에 필요한 파일 크기를 줄여줍니다

기본적으로 gzip이라는 압축 알고리즘을 사용하고 확장자는 .gz 라는 확장자명을 사용하게 됩니다!

  • 콘텐츠가 포함된 최대 페인트요소!

자바스크립트 줄이기

안쓰는 자바스크립트를 줄이기 위해서는 무엇을 해야할까?

바로 Lazy loading(지연 로딩) 이다!

초기 로딩시에 모든 자바스크립트 파일을 가져오는 것이 아니라, 필요한 순간에 해당 코드를 동적으로 불러와 번들의 크기를 줄이고, 초기 로딩 속도를 개선하는 것이다!

어떻게 하는지 알아보자!

before

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { path: "/", element: <App /> },
      { path: "login", element: <Login /> },
      { path: "login/find", element: <Find /> },
      { path: "signup", element: <SignUp /> },
      { path: "signin/update/password", element: <Password /> },
      { path: "admin/login", element: <AdminLogin /> },
      {
        element: <ProtectedRoute />,
        children: [
          { path: "inspection", element: <Inspection /> },
          { path: "timeline/:inspectionId", element: <TimeLIne /> },
          { path: "myinfo/update/", element: <Myinfo /> },
        ],
      },
      {
        element: <AdminRoute />,
        children: [{ path: "admin/dashboard", element: <AdminDashboard /> }],
      },
      { path: "*", element: <NotFound /> },
    ],
  },
]);

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <QueryClientProvider client={queryClient}>
        <RouterProvider router={router} />
      </QueryClientProvider>
    </ErrorBoundary>
  </StrictMode>

after

const Login = lazy(() => import("./pages/login/Login"));
const Layout = lazy(() => import("./components/common/Layout"));
const SignUp = lazy(() => import("./pages/signup/SignUp"));
const TimeLine = lazy(() => import("./pages/timeline/TimeLine"));
const Inspection = lazy(() => import("./pages/inspection/Inspection"));
const Find = lazy(() => import("./pages/login/find/Find"));
const ProtectedRoute = lazy(() => import("./components/common/ProtectedRoute"));
const Password = lazy(() => import("./pages/password/Password"));
const MyInfo = lazy(() => import("./pages/myinfo/update/MyInfo"));
const NotFound = lazy(() => import("./components/common/NotFound"));
const AdminLogin = lazy(() => import("./pages/admin/login/AdminLogin"));
const AdminDashboard = lazy(
  () => import("./pages/admin/dashboard/AdminDashboard")
);
const AdminRoute = lazy(() => import("./components/admin/AdminRoute"));

const queryClient = new QueryClient({
  defaultOptions: {},
});

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [ .... 중략....
    }]);
    
createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <Suspense fallback={<>loading 중...</>}>
      <QueryClientProvider client={queryClient}>
        <RouterProvider router={router} />
      </QueryClientProvider>
    </Suspense>
  </StrictMode>
);

react의 lazy를 사용해서 컴포넌트들을 해당 페이지에 진입했을 때만 로드 될 수 있게 했다!

위와 같이 적용하면 페이지에 대한 내용을 하나의 청크에 묶지 않고, 각 페이지 별로 청크 파일이 따로 생성되며,

해당 페이지에 진입하지 않으면 다른 페이지의 내용을 불러오지 않는다!

이미지 최적화 하기!

vite에서 다양한 플러그인을 사용해서 여러 포맷들의 이미지 최적화를 진행할 수 있다!

적용한 코드를 한 번 보자!

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
import svgr from "vite-plugin-svgr";
import viteCompression from "vite-plugin-compression";
import viteImagemin from "@vheemstra/vite-plugin-imagemin";
import imageminWebp from "imagemin-webp";
import imageminSvgo from "imagemin-svgo";
import imageminPngquant from "imagemin-pngquant";
import imageminMozjpeg from "imagemin-mozjpeg";
import { viteStaticCopy } from "vite-plugin-static-copy";

// https://vitejs.dev/config/ß
export default defineConfig({
  base: "/",
  plugins: [
    viteStaticCopy({
      targets: [
        {
          src: "src/assets/*",
          dest: "assets",
        },
      ],
    }),
    viteImagemin({
      plugins: {
        jpg: imageminMozjpeg(),
        svg: imageminSvgo(),
        png: imageminPngquant(),
      },
      makeWebp: {
        plugins: {
          jpg: imageminWebp(),
          png: imageminWebp(),
          svg: imageminWebp(),
        },
      },
    }),
    react(),
    viteCompression({
      algorithm: "gzip",
      ext: "gz",
    }),
    svgr({
      svgrOptions: {
        icon: true,
      },
    }),
  ],
  server: {
    host: "0.0.0.0",
    port: 3000,
    open: false,
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
      "@components": path.resolve(__dirname, "./src/components"),
    },
  },
});
  • @vheemstra/vite-plugin-imagemin: Vite에서 이미지 최적화를 자동으로 처리해주는 플러그인입니다. 빌드 시점에 이미지를 압축하여 최적화된 이미지 파일을 생성하므로, 페이지 로드 속도 개선에 도움이 된다!
  • imagemin-webp: 이미지를 WebP 형식으로 변환해주는 플러그인이다. WebP는 PNG나 JPEG보다 더 작은 파일 크기를 가지면서도 품질을 유지할 수 있어, 웹사이트에서 이미지 로딩 속도를 높이는 데 유용하다!
  • imagemin-svgo: SVG 형식의 이미지를 최적화하는 플러그인이고 SVG 파일의 불필요한 메타데이터, 주석 등을 제거해 파일 크기를 줄여주며, 그래픽의 품질을 유지하면서 효율성을 높인다!
  • imagemin-pngquant: PNG 이미지를 최적화하기 위한 플러그인으로, PNG 파일의 크기를 줄여주는 역할을 한다. pngquant는 이미지의 투명도와 품질을 유지하면서도 용량을 줄이기 때문에 웹사이트에서 많이 사용된다!
  • imagemin-mozjpeg: JPEG 이미지를 압축하는 플러그인입니다. mozjpeg는 JPEG 이미지의 크기를 최소화하면서도 시각적인 품질을 유지하도록 최적화하는 도구로, JPEG 이미지 로딩 성능을 개선한다!

근데 이미지 최적화에 대한 라이브러리나 자료들을 찾아보면서 많고 좋은 정보들을 봤지만, 이런 글을 봤다

“이미지를 최적화하기전에 이미지를 최적화할 필요가 있는지부터 확인해보아야한다.”

https://oliveyoung.tech/blog/2021-11-22/How-to-Improve-Web-Performance-with-Image-Optimization/

정말 맞는 말이다. 지금 회사에서 제공하는 것은 차트나, 게시판 등의 기능이 중심이기 때문에, 이미지가 주로 사용되거나 중요한 요소가 아니다. 그래서 큰 노력은 기울일 필요가 없다고 생각드는 한 편, 이미지가 작더라도 페이지 로딩 속도로 사용성을 개선하는 행동이라면 의미있는 것 아닐까? 라는 생각에 다시 한 번 잡아봤다!

vheemstra/vite-plugin-imagemin을 사용해 최적화를 한 결과

오잉 ..? 최적화를 돌렸는데 왜 저 이미지들이 다 변환되지 않은거지?

분명 assets 폴더에 다 있어야 하는데?

빌드 프로세스에서 왜 이미지들이 복사되지 않는걸까?

사용하지 않는 리소스들은 vite 가 트리쉐이킹 한다고 했는데, 다른 이미지들은 사용하는걸..?

문제는 이것이었다!

//경로를 직접 참조하는 이미지 태그만 트리쉐이킹에서 걸러지지 않았다!
<img
              src={CancerVETLogo}
              alt="CancerVet Logo"
              className="h-10 w-auto mr-2"
            />
            
 //하지만 필자는 svg를 유연하게 컨트롤하기 위해서 svg 플러그인을 써서 컴포넌트 형식으로 사용했다!
 import DownloadLogo from "@/assets/download-icon.svg?react";
               <DownloadLogo className="w-7 h-7 mr-2" /> 
 

위의 플러그인을 사용한 컴포넌트들이 트리쉐이킹 되버리면서 바로 아래의 빌드 결과물이 생겼다!

그래서 찾은 것은 vite-plugin-static-copy!

src/asset 의 폴더 모든 이미지를 dist/assets 으로 강제 복사 하게 만들었다!

위에서도 썼지만..

viteStaticCopy({
      targets: [
        {
          src: "src/assets/*",
          dest: "assets",
        },
      ],
    }),
    
    //vite confing의 plugin에 이와 같은 구문을 추가했다.

src/assets 에 있는 모든 이미지들이 webp로 압축되었다!!!

btob 서비스라 검색엔진 최적화에는 신경쓰지 않았다..
이 서비스가 노출되어 홍보가 필요하다면 모를까?
여튼 우여곡절 끝에 최적화를 완료했다!

물론 맨 위의 나와있던 점수는 로컬환경이고, 아래에 있던 점수는 배포환경이다.
로컬환경에서 개발 서버를 실행할 때는 일반적으로 최적화된 상태로 빌드되지 않기 때문에 차이가 더욱
클 수 밖에는 없을 것이다.

그러나 아직 최적화 해야할 페이지들은 많긴하다.. 🥲
웹 상에서 더욱 보여줘야할게 많아진 요즘 생태계에서 어떻게 최적화하는지 아는 것은 굉장히 중요해진다는 것을
또 한 번 실감한다!
열심히 학습해서 계속 개선 해보자고~!

profile
새로운 걸 배우는 것을 좋아하는 프론트엔드 개발자입니다!

0개의 댓글