Code Splitting 이란?
코드 스플리팅(Code Splitting)
은 애플리케이션의 코드를 필요한 시점에 로드할 수 있도록 여러 개의 작은 파일로 분리하는 기술입니다.
애플리케이션이 커질수록 CSS와 JavaScript 번들 크기가 커지는데, 이를 그대로 로드하면 초기 로딩 속도가 느려질 수 있습니다. 코드 스플리팅을 활용하면 초기 화면에 필요한 코드만 로드
하고, 나머지 코드는 필요한 시점에 Lazy Loading 방식으로 불러올 수 있어 성능을 개선
할 수 있습니다.
주로 Webpack과 같은 모듈 번들러를 사용해 구현하며, 이를 통해 애플리케이션의 초기 로딩 시간을 줄이고 사용자 경험을 향상시킬 수 있습니다.
현재 프로젝트에서, Code Splitting을 하는 이유?
리액트 어플리케이션의 경우 빌드를 통해서 파일을 배포하게 되고, 이 과정에서, 파일 크기를 최소화하는 것이 바람직하다.
파일 크기가, 너무 클 경우, 리액트 어플리케이션의 초기 로딩속도가 상당히 느려지므로, 이는 결과적으로, User Experience
에 영향을 미치게 됩니다.
pnpm run build
tsx
이렇게, 빌드를하게 되면 위의 이미지 처럼 기존에 하나의 자바스크립트 파일에, 내가 만든 모든, 리액트 파일이 담긴 것을 알 수 있다.
사실 지금 실제 서비스로 운영하고 있는 서비스의 구조는 아래와 같습니다.
📦 Crabit
┣ 📂 Crabit (not-login)
┃ ┣ 📂 / (홈페이지)
┃ ┣ 📂 /crabit-challenge (크래빗 챌린지)
┃ ┣ 📂 /crabit-scholarship-card (크래빗 장학카드)
┃ ┣ 📂 /crabit-pricing (요금제)
┃ ┣ 📂 /signup (회원가입)
┃ ┣ 📂 /password-find (비밀번호 변경)
┣ 📂 Crabit(login)
┃ ┣ 📂 마이페이지 관련
┃ ┣ 📂 대시보드 관련
┃ ┣ 📂 챌린지 관련
tsx
현재 제가 만든 서비스는 약 40개의 페이지로 구성되어 있지만, 로그인하지 않은 사용자가 실제로 접근할 수 있는 페이지는 6개에 불과
합니다.
모든 사용자가 로그인 후 서비스를 이용하는 것이 바람이지만, 현실적으로는 서비스를 사용하지 않고 랜딩 페이지만 확인하는 사용자도 상당히 많습니다. 이런 상황에서 로그인하지 않은 사용자 조차 불필요하게 로그인이 필요한 나머지 30개 이상의 페이지에 대한 JavaScript 번들을 다운로드를 하게 된다면, 불필요하게 서버의 자원을 낭비
하여, 통신이 느린 환경에서 더더욱 초기 접속 속도가 느려지는 불편함을 겪게 됩니다.
React 애플리케이션은 규모가 커질수록 사용하는 라이브러리와 유틸리티의 크기가 함께 증가하며, 이는 번들 크기에 직접적인 영향을 미칩니다. 결과적으로 사용자가 웹 페이지를 처음 방문할 때 로드해야 할 파일의 크기가 커지고, 특히 인터넷 속도가 느린 환경에서는 초기 접속 속도가 매우 느려져 사용자 경험에 부정적인 영향을 미칩니다.
이를 해결하기 위해 코드 스플리팅
과 같은 기법을 통해 초기 로드에 필요한 코드만 불러오고, 나머지는 필요한 시점에 로드하는 방식이 필요합니다.
해결 방법
1. Code Splitting과 Lazy Loading
Code Splitting
과 Lazy Loading
을 사용하여 초기 로딩 속도를 개선하고 불필요한 번들 로드를 줄이는 방식을 채택합니다.
Lazy Loading
React.lazy
를 사용하면 컴포넌트를 처음 렌더링하는 시점까지 해당 컴포넌트의 코드를 로드하지 않습니다. 이는 컴포넌트를 필요한 시점에 동적으로 로딩하여 애플리케이션의 성능을 향상시킵니다.
장점
- 번들을 여러 개의
chunk
로 분할하여, 초기 로딩 시 필요한 코드만 로드. 데이터 사용량 절감
및빠른 로딩
으로더 나은 사용자 경험 제공
.- 중요도가 떨어지거나, 당장 화면에 보이지 않는, 요소들의 번들의 로딩들을 우선적으로 시행하지 않으면서, 로딩 퍼포먼스를 최적화 할 수 있음.
예제
import React, { Suspense } from 'react';
// React.lazy를 통해, 컴포넌트를, Lazy Loading을 합니다.
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
// 로딩을 하는 동안, 보여줄 fallback UI를 보여줄 수 있습니다.
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
tsx
Code Splitting
Code Splitting
은 클라이언트에 불필요한 코드가 전달되지 않도록 하고, 적절한 시점에 필요한 코드만 동적으로 로드할 수 있도록 분리하는 기법입니다.
장점
- 큰 번들 파일을
작은 chunk로 분할.
초기 로딩 속도 개선
및상호작용 시간 단축
.사용자가 빠르게 콘텐츠를 보고 상호작용
할 수 있어사용자 경험 향상
.
2. Suspense
Suspense
는 동적으로 로드되는 컴포넌트의 로딩 상태를 처리하기 위한 컴포넌트입니다. 로딩 중 화면에 표시할 UI를 fallback
으로 정의하여 사용자 경험을 부드럽게 만듭니다.
예제:
<Suspense fallback={<div>Loading content...</div>}>
<LazyComponent />
</Suspense>
tsx
정리된 장점
-
초기 로드 속도 향상
초기 페이지에서 필요한 코드만 로드하므로 페이지 로딩 속도가 빨라집니다.
-
데이터 사용량 절감
필요한 시점에만 데이터를 로드하므로 사용자의 네트워크 사용량이 줄어듭니다.
-
사용자 경험 개선
빠른 로딩과 상호작용으로 유저 이탈률을 줄이고 만족도를 높일 수 있습니다.
이를 통해 우리의 애플리케이션은 불필요한 리소스 낭비를 줄이고, 더 나은 성능과 사용자 경험을 제공할 수 있습니다.
Code Splitting 도입 결과
실제로, 현재 운영하고 있는 서비스에서, Code Splitting
작업을 통해,
기존: 1276.99 kB
현재 934.81 kB
약, 342kB의 초기 다운로드되는, 패키지 크기를 줄일 수 있었다.
여담으로, 빠른 네트워크에서는, 300kB
의 패키지 크기를 줄인 것이, 엄청난 차이는 아닐 수 있지만, 3G 속도(약 1Mbps 기준)로는 300kB를 다운로드하는 데 약 2.4초
가 걸립니다. 인터넷 환경이 좋지 않은, 사람들이현재 서비스를 이용할 떄 이 시간은 사용자에게 느린 초기 로딩으로 다가올 수 있습니다.
Before
After
Code Splitting시 유용한 방법
1. TreeShaking
→ 라이브러리 전체 중, 일부만 사용한다면, 부분 설치를 하자.
lodash
는, 자바스크립트의 정말, 인기있는 라이브러리 중 하나입니다. 배열이나, 컬렉션, 날짜 등 데이터의 필수적인 구조를 쉽게 다룰 수 있게끔 하는데 사용됩니다.
그래서, lodash
의 경우, 정말 유용한 기능들이 많이 내장되어 있어서, 패키지의 크기가 큽니다! 하지만, 현재 서비스에서 만약에 lodash의 debounce 밖에 사용하지 않는데, 정말 lodash 패키지 전체를 설치 할 필요가 있을까요?
우리는, 이를 트리 쉐이킹
이라고 부릅니다. ‘필요한 것만 가져다 쓴다’
라고 생각하면 됩니다.
아래 예시 코드를 봐볼까요?
pnpm install lodash
tsx
// lodash에서 debounce 가져오기
import _ from 'lodash';
const handleResize = _.debounce(() => {
console.log('Window resized');
}, 300);
window.addEventListener('resize', handleResize);
tsx
위 코드에서는 lodash
의 전체 패키지를 설치하고, debounce를 사용합니다. 그러나 이는 lodash
의 모든 유틸리티 함수가 번들에 포함되어 번들 크기가 불필요하게 커지는 결과를 초래합니다.
트리 쉐이킹
을 우리는, lodash
의 라이브러리 중, 사용하는 일부만 설치하여, 전체 패키지를 설치하는 것이 아닌, 필요한 일부만, 설치하여 서버의 리소스가 낭비되는 것을 막을 수 있습니다.
pnpm install lodash.debounce
tsx
// lodash.debounce만 가져오기
import debounce from 'lodash.debounce';
const handleResize = debounce(() => {
console.log('Window resized');
}, 300);
window.addEventListener('resize', handleResize);
tsx
위 코드는 lodash.debounce
만을 설치하여 필요한 기능만 번들에 포함합니다. 이는 트리 쉐이킹의 일환으로, 코드 최적화에 유리합니다.
rollup-plugin-visualizer을 사용한, 번들 시각화
내가 사용하고 있는, 번들의 크기를, 빌드했을 때 체크할 수 있다, vite 이전에, WebPack Bundle Analyzer
유사한 역할을 한다.
pnpm install --save-dev rollup-plugin-visualizer
tsx
해당 파일 설치 후 vite.config.ts에 아래와 같이 설정을 추가로 해주면됩니다.
// 추가
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
react(),
// 추가
visualizer({
filename: './dist/report.html',
open: true,
brotliSize: true,
}),
svgr(),
],
build: {
sourcemap: true,
},
});
tsx
pnpm run build
tsx
빌드 이후에, 바로 아래와 같은 사진이 나오게 됩니다.
라이브러리의 번들 크기가, 너무 크고 혹시라도 대체제가 있다면, 다른 대체 라이브러리를 설치하는 것으로, 웹 애플리케이션의 빌드 시간을 개선할 수 도 있고, 위에서 설명드린, 다양한 기법들로도 이를 개선할 수 있습니다.