사용자가 방문하지 않은 화면의 코드까지 처음부터 내려받을 이유는 없어. 코드 분할로 현재 경험에 필요한 JavaScript만 보내는 방법을 익혀 보자.
동적 import가 chunk 경계를 만들어
const Settings = lazy(() => import('./Settings'))처럼 import를 함수 안에 두면 Vite와 Rollup이 Settings를 별도 chunk로 만들어. 런타임에서는 Settings를 처음 렌더링할 때 그 파일을 받고, React는 기다리는 동안 가장 가까운 Suspense의 대체 UI를 보여 줘.
같은 모듈을 다른 곳에서 정적으로 import하면 메인 bundle에도 포함될 수 있어. 빌드 뒤 dist/assets와 브라우저 Network 패널을 함께 봐야 실제 경계가 생겼는지 확인할 수 있어.
사용자가 구분하는 경험 단위로 나눠
- Route는 가장 자연스러운 경계야. 홈을 방문한 사용자는 admin 화면의 비용을 낼 필요가 없어.
- 무거운 기능은 chart, Markdown editor, image editor처럼 일부 화면에서만 쓰는 큰 의존성을 따로 받을 수 있어.
- Modal과 dialog는 사용자가 열기 전까지 필요하지 않다면 늦게 받아도 돼.
작은 utility 함수마다 경계를 만들면 요청 수와 관리 비용만 늘어. 사용자가 별도의 로딩 상태를 이해할 수 있는 단위인지 먼저 물어봐.
Chunk에도 네트워크 비용이 있어
별도 chunk 하나마다 요청과 parsing 비용이 생겨. 너무 잘게 나누면 작은 요청이 연달아 이어지는 waterfall 때문에 큰 bundle 하나보다 느려질 수 있어. 30KB에서 200KB 정도의 gzip 결과는 출발점일 뿐이고, 실제 판단은 사용자 기기와 네트워크에서 측정해야 해.
첫 화면의 제목, navigation, 주요 동작처럼 바로 보여야 하는 내용은 지연 로딩하지 마. 항상 필요한 화면을 늦추면 짧은 loading flash만 추가되고 초기 경험은 더 나빠져.
곧 필요할 화면은 미리 받아
사용자가 Settings 링크에 pointer를 올렸거나 브라우저가 한가해졌다면 해당 module을 미리 요청할 수 있어. HTML의 modulepreload를 사용하거나 같은 동적 import를 먼저 호출해 cache를 데울 수 있어. Vite도 entry chunk의 직접 의존성에는 modulepreload hint를 자동으로 만들어.
미리 받기는 추측이 틀리면 사용하지 않을 bytes를 소비해. 자주 이어지는 navigation처럼 근거가 있는 경로에만 적용하고 Network 패널에서 실제 요청 시점을 확인해.