리액트 애플리케이션의 경우 빌드를 통해서 배포를 합니다.
이 과정에서 파일 크기를 가능하면 최소화하여야 하는데, 그 이유는 파일 크기가 성능을 결정하고 결과적으로 성능은 사용자 경험에 영향을 주기 때문입니다.
Vite: 빠른 속도와 간편한 개발을 할 수 있도록 하는 빌드 도구
코드 스플리팅은 애플리케이션을 여러 개의 작은 청크*로 나눠 초기 로딩 속도를 단축하고 필요 시에만 특정 청크를 로드하는 방식입니다. rollupOptions의 manualChunks 옵션을 활용하여 코드 분할을 설정할 수 있습니다.
// vite.config.ts 설정
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import gzipPlugin from 'rollup-plugin-gzip'
import tsconfigPaths from "vite-tsconfig-paths"
export default defineConfig({
plugins: [react(), tsconfigPaths()],
build: {
rollupOptions: {
plugins: [gzipPlugin()],
output: {
manualChunks: (id) => {
if (id.indexOf("node_modules") !== -1) {
const module = id.split("node_modules/").pop();
if (module) {
return `vendor-${module.split("/")[0]}`;
} else {
return 'vendor-unknown';
}
}
},
}
},
},
})
위의 설정은 node_modules 에 있는 라이브러리들을 vender 청크로 분리하는 방법으로, vender 청크가 캐싱되어 변경되지 않으면 재사용될 수 있도록 합니다.
첫번째 파라미터인 id에서 node_modules가 있는지 찾고 node_modules를 스플릿해 vender라는 prefix를 붙인 module을 넣어줍니다.

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [react(), tsconfigPaths()],
build: {
target: 'esnext', // 1. 최신 브라우저 타겟팅
minify: 'esbuild', // 2. esbuild를 사용하여 빌드 속도 개선 -> 이미 기본 값이므로 따로 설정하지 않아도 됨
sourcemap: false, // 3. 프로덕션에서는 소스맵 비활성화
chunkSizeWarningLimit: 500, // 4. 청크 경고 한계 조정
},
optimizeDeps: {
esbuildOptions: {
target: 'esnext', // 5. 최신 JS 문법 사용
},
},
});
target: 'esnext' minify: 'esbuild'esbuild는 매우 빠른 속도로 코드를 난독화하고 압축하는 도구로 Vite는 기본적으로 esbuild를 사용하여 빌드 속도를 개선합니다.sourcemap: false chunkSizeWarningLimit: 500 esbuildOptions esbuildOptions를 통해 esbuild의 설정을 조정할 수 있으며, 여기서는 esnext를 타겟팅하여 최신 JS 문법을 사용하도록 설정했습니다.vite-plugin-image-optimizer 를 사용해 이미지 크기를 줄여 최적화했습니다.
pnpm add vite-plugin-image-optimizer -D // 설치
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import gzipPlugin from 'rollup-plugin-gzip'
import tsconfigPaths from 'vite-tsconfig-paths'
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
tsconfigPaths(),
ViteImageOptimizer({
jpg: { quality: 75 },
png: { quality: [0.6, 0.8] },
webp: { quality: 80 },
exclude: ['example.webp', '...'], // 압축 제외할 파일
}),
],
...
})

이미지를 webp 포멧으로 변환하고, loading="lazy" 속성을 추가하여 페이지 로딩 속도를 개선했습니다.
<img src="/images/example.webp" alt="example" loading="lazy" />
loading="lazy" 는 이미지를 로딩할 때 렌더링이 블로킹되지 않도록 개선하는 방법입니다.loading="lazy" 속성이 적용된 이미지를 제대로 인식하지 못할 수 있기 때문에 중요한 이미지에는 사용을 지양해야 합니다.필요할 때만 컴포넌트를 로딩하는 방법으로 페이지에 필요한 순간에만 컴포넌트가 로드되므로 초기 로딩 속도가 빨라집니다.
리액트 내장 컴포넌트로 코드 스플리팅된 컴포넌트를 로딩하고 로딩 중에 보여질 UI를 설정할 수 있습니다. fallback이라는 Props를 통해 보여질 컴포넌트를 지정할 수 있습니다.
import { lazy, Suspense } from 'react';
import Background from '@components/molecules/Background/Background';
import style from './SlideContent.module.scss';
const Home = lazy(() => import('@/components/templates/Home/Home'));
...
const SlideContent = () => {
const slides = [
<Home openModal={() => openContentModal('home')} />,
...
];
return (
<Suspense fallback={<Background />}>
<SwiperSlide key={index + "slide key"}>{slide}</SwiperSlide>
</Suspense>
);
};
export default SlideContent;
useMemo(값을 메모제이션), useCallback(함수를 메모제이션)을 활용해 불필요한 리렌더링을 방지하기React.memo를 활용해 컴포넌트 리렌더링을 방지하기(동일한 prop가 전달된다면 리렌더링되지 않음)버튼에 aria-label 속성을 활용해 어떤 의미가 있는지 설정
시멘틱 태그로 변경
img에 alt 이미지 대체 텍스트 명시
pagination에 tabIndex를 적용해 tab으로 접근 가능하도록 설정
robots.txt 설정
// 모든 검색엔진의 로봇에 대해 수집 허용으로 설정
User-agent: *
Allow: /
LCP 이미지 미리 불러오도록 설정 및 비동기 처리
// <head> 태그 안에 추가
<link rel="preload" as="image" href="/images/run/eunjee-run.webp">
<img
decoding="async"
src="example.webp"
alt="example에 대한 대체텍스트"
/>


개선 전에 없었던 기능들(터치 슬라이드, hooks 추가, 최적화 등)로 인해 전체적인 코드량은 늘어났지만 성능 자체는 눈에 띄게 개선되었습니다. 실제 서비스 운영 시, 사용자 이탈 및 검색엔진 최적화를 위해 코드 분할, SEO 최적화 등 최적화에 더욱 신경써야 겠다고 생각하게 됐습니다.