정신없이 프로젝트를 마무리 한 뒤.... 두번째 배포 즈음,
라이트하우스로 성능을 체크했는데 너무나도 충격적인 결과에 무릎을 꿇고 말았다..
1) Stack
이나 <Box />
같은 컴포넌트에 sx={{ ... }}
스타일링 바로 추가했던 방식을 변경했다.
상수와 스타일을 정의하여 렌더링을 최적화 할 수 있도록 수정했고
CI 로고 떄문에 이미지가 몇 장 있었는데, webp
확장자로 바꿔줬다.
나는 이미지가 세 개 밖에 되지 않아서 그냥 바꾼걸로 등록했지만
이미지가 많다면 vite-plugin-imagemin
라이브러리를 사용하거나,
또는 vite-plugin-image-optimizer
를 사용하면된다!
1) 레이지 로딩, 특정 페이지에서는 사용하지 않았는데 협의 후 추가하기로해서 전부다 적용 완.
리액트의 레이지 로딩(Lazy Loading)이란 웹 애플리케이션의 초기 로딩 시간을 개선하기 위해 사용되는 기술입니다. 이 기술은 필요한 시점까지 특정 컴포넌트나 리소스의 로딩을 지연시키는 방식으로 작동합니다.
React.lazy()
이 함수를 사용하여 동적으로 컴포넌트를 임포트할 수 있습니다.
Suspense
레이지 로딩된 컴포넌트가 로드되는 동안 로딩 상태를 표시할 수 있는 컴포넌트입니다.
1) 웹 서버에서 텍스트 기반 리소스(HTML, CSS, JavaScript 등)를 압축하여 제공함으로써 네트워크 사용량을 줄일 수 있다고 한다.
gzip
: 가장 널리 사용되는 웹 콘텐츠 압축 방식 중 하나로, 텍스트 파일을 효과적으로 압축합니다.
deflate
: gzip과 비슷한 압축 알고리즘으로, 일부 오래된 브라우저에서도 지원됩니다.
Brotli
: Google이 개발한 비교적 새로운 압축 알고리즘으로, gzip보다 더 효율적인 압축률을 제공합니다.
1) vite-plugin-compression
라이브러리를 사용하면 된다!
plugins: [
react(),
viteTsconfigPaths(),
svgrPlugin(),
legacy({
targets: ["edge >= 87", "chrome >= 90"],
modernPolyfills: true,
}),
viteCompression({
algorithm: "gzip",
ext: ".gz",
}),
// Brotli 압축 추가
viteCompression({
algorithm: "brotliCompress",
ext: ".br",
}),
],
나는 두 압축 알고리즘(gzip과 brotli)을 동시에 사용했다.
이렇게 설정하면 Vite
는 빌드 시 두 가지 버전의 압축 파일을 모두 생성하게 되는데,
.gz 확장자를 가진 gzip 압축 파일
.br 확장자를 가진 brotli 압축 파일
이런 설정의 장점은:
더 넓은 브라우저 호환성: 모든 현대 브라우저가 gzip을 지원하므로 기본적인 호환성 보장
최적의 압축률: brotli는 일반적으로 gzip보다 15-25% 더 나은 압축률을 제공
최적화된 전송: 서버는 클라이언트가 지원하는 최적의 압축 형식을 선택할 수 있음
실제로 이 설정이 적용되면 웹 서버는 클라이언트의 Accept-Encoding 헤더를 확인해서,
brotli를 지원하는 브라우저에는 .br
파일을,
gzip만 지원하는 브라우저에는 .gz
파일을,
둘 다 지원하지 않는 경우 원본 파일을 제공한다고 한다!
둘 다 지원하지 않는 경우는 아주아주아주아주아주 드물지만.. 금융권쪽에는 드물게 있을 수 있을지도?.. 그리고 이런 점 때문에 둘 다 사용하는 것도 있다 ㅎ
1) build
> rollupOptions
> manualChunks
에서 스플리팅 해주고, optimizeDeps
에서 특정 라이브러리를 단일 인스턴스만 사용가능하게 설정.
build: {
minify: "esbuild",
cssMinify: true,
cssCodeSplit: true,
commonjsOptions: { transformMixedEsModules: true },
chunkSizeWarningLimit: 1000,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("@mui/icons-material")) {
return "mui-icons";
} else if (id.includes("@mui")) {
return "mui-core";
}
else if (id.includes("toast-ui")) {
return "toast-ui-legacy";
} else if (id.includes("node_modules")) {
return id
.toString()
.split("node_modules/")[1]
.split("/")[0]
.toString();
}
},
},
treeshake: {
moduleSideEffects: false,
},
},
},
optimizeDeps: {
exclude: ["@toast-ui/grid", "@toast-ui/chart", "@toast-ui/react-grid"],
dedupe: ["react", "react-dom", "react-is", "@mui/utils"],
},
이렇게 하고있는데, 그래도 mui core 및 toast-ui 에서 큰 용량으로 줄여지고있어서, 좀 더 고민해봐야할 것 같다...
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import viteTsconfigPaths from "vite-tsconfig-paths";
import svgrPlugin from "vite-plugin-svgr";
import legacy from "@vitejs/plugin-legacy";
import path from "path";
import viteCompression from "vite-plugin-compression";
export default defineConfig((command, mode) => {
const env = loadEnv(mode, process.cwd(), "");
return {
base: `${env.VITE_BASE_URL}`,
experimental: {
renderBuiltUrl() {
return { relative: true };
},
},
plugins: [
react(),
viteTsconfigPaths(),
svgrPlugin(),
legacy({
targets: ["edge >= 87", "chrome >= 90"],
modernPolyfills: true,
}),
viteCompression({
algorithm: "gzip",
ext: ".gz",
}),
viteCompression({
algorithm: "brotliCompress",
ext: ".br",
}),
],
build: {
minify: "esbuild",
cssMinify: true,
cssCodeSplit: true,
commonjsOptions: { transformMixedEsModules: true },
chunkSizeWarningLimit: 1000,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("@mui/icons-material")) {
return "mui-icons";
} else if (id.includes("@mui")) {
return "mui-core";
}
else if (id.includes("toast-ui")) {
return "toast-ui-legacy";
} else if (id.includes("node_modules")) {
return id
.toString()
.split("node_modules/")[1]
.split("/")[0]
.toString();
}
},
},
},
},
optimizeDeps: {
exclude: ["@toast-ui/grid", "@toast-ui/chart", "@toast-ui/react-grid"],
dedupe: ["react", "react-dom", "react-is", "@mui/utils"],
},
resolve: {
alias: {
"@toast-ui": path.resolve(__dirname, "node_modules/@toast-ui"),
},
},
};
});
36점에서.. 73점이라니 정말..!
사용하지 않는 자바스크립트 줄이기는 개선을 좀 더고민해봐야 하는게,
지금 한 레퍼지토리에서 다른 서비스가 두개가 올라가 있는데,
내가 사용하진 않아도... 다른 팀원이 사용하는 것이 있어서.... 시간이 되실때 이야기해서 개선해나가기로 했다.
목표는 최~소한 85점이였기 때문에, 시간상 이정도로 만족해야 하는게 슬프지만
그래도 너무 뿌듯하다.
treeshake: { moduleSideEffects: false }
사실 이 설정을 사용해보고싶었다.
이 설정은 번들링 과정에서 "사이드 이펙트(부수 효과)"가 없는 모듈을 더 공격적으로 최적화할 수 있게 해주는데 포함시키지 않은 이유는,
이 설정을 false
로 지정하면 Rollup(Vite의 내부 번들러)은 실제로 사용되지 않는 코드를 더 효과적으로 제거할 수 있는데,
이는 번들 크기를 상당히 줄일 수 있지만, 모듈이 실제로 사이드 이펙트를 가지고 있다면 예상치 못한 동작이 발생할 수 있다.
나의 경우 AKS 로 반입될 이미지 버전에서 React Router DOM의 사이드 이펙트를 트리 쉐이킹 과정에서 "사용되지 않는다"고 잘못 판단되어 제거되더라... 그래서 오류가..어흫ㄱ..
manualChunks(id)
아무튼... 나름(?) ..?