"Bundling은 목적이 아니라 배포 환경의 제약을 푸는 수단이야. Browser, server, CLI가 서로 다른데 같은 build 습관을 물려주면 비용만 남아."
Bundler는 browser의 오래된 제약에서 출발했어
2010년대 browser는 ES module을 직접 불러오지 못했어. 작은 module 100개를 따로 받는다면 page 하나를 여는 동안 network 요청도 100번 생겼지. Browserify와 webpack, 그 뒤의 Rollup, esbuild, swc, Vite, Turbopack 같은 도구가 import graph를 읽어 이 문제를 풀었어.
- Import graph를 미리 따라가 필요한 module을 모아.
- 많은 module을 몇 개의 output file로 합쳐.
- Code를 minify하고 쓰지 않는 부분을 tree-shake하며, 오래된 browser가 이해할 문법으로 낮춰.
현대 browser가 native ESM과 HTTP/2·HTTP/3를 지원해도 production bundling이 사라지지 않은 이유는 요청 수만이 아니야. Tree-shaking, chunk 구성, 압축처럼 browser delivery에 필요한 일이 여전히 남아 있거든.
일반적인 Node server에는 같은 이유가 없어
Node server는 local disk의 module을 같은 process 안에서 읽어. Import마다 network 왕복을 하지 않고 first paint 예산도 없어. Browser 때문에 생긴 bundling 이유를 server에 그대로 가져오면 안 돼.
Template가 넣어 줬다는 이유만으로 TypeScript server를 번들하면 개발과 CI에 build 단계가 하나씩 생겨. Debugger는 source map을 거쳐야 하고, runtime이 이미 이해하는 문법도 다시 변환하지. Node 22 이상에서 지울 수 있는 TypeScript만 쓴 service라면 node --env-file=.env server.ts처럼 source를 직접 실행할 수 있어. Build artifact가 실제로 필요한지 먼저 증명해.
Node에서도 bundle이 값을 하는 경우
- Single Executable Application(SEA) — 실행 파일 하나의 입력으로 code를 모아야 할 때 esbuild나
bun build가 잘 맞아. - FaaS 배포 — AWS Lambda나 Vercel Functions처럼 큰 배포 directory가 cold start에 영향을 주는 환경에서는 bundle이 크기를 줄여. Next.js 같은 framework가 이 일을 내부에서 맡기도 해.
- CLI 단일 file 배포 — 사용자가
npx your-cli로 받을 때 transitive dependency 전체보다 bundle 하나가 다루기 쉬울 수 있어. - 무거운 library의 tree-shaking — lodash 일부만 쓴다면 bundler가 실제로 가져온 code만 남길 수 있어. Node는 module을 local disk에서 빠르게 읽을 뿐, 사용하지 않는 code를 자동으로 떼어 내지는 않아.
이 조건에 해당하지 않는 장기 실행 server라면 source 직접 실행이 더 단순할 가능성이 커. “Node project니까 build”가 아니라 “이 배포물이 무엇을 요구하나”로 결정해.
Frontend는 delivery target이 달라
Browser로 보내는 code에는 production bundling이 여전히 중요해. Vite는 개발 중에는 native ESM을 이용해 module을 필요할 때 변환해 제공하고, production에서는 최적화된 bundle을 만들어. esbuild와 swc 덕분에 예전 webpack pipeline보다 훨씬 빠르게 변환할 수 있어. 개발 server가 unbundled라고 해서 최종 사용자에게도 source file을 그대로 보낸다는 뜻은 아니야.
Monorepo 안에서도 surface마다 다르게 골라
apps/web은 Vite나 Next.js를 써서 browser용 production output을 만들어.apps/api는 Node가 source를 직접 실행하고 별도 bundle을 만들지 않을 수 있어.packages/shared는workspace:로 두 application이 함께 쓰며 type check만 하고, 소비자가 각자 필요한 방식으로 처리하게 둘 수 있어.
한 monorepo에 있다고 build 전략까지 같을 필요는 없어. Browser에는 browser의 delivery 비용이 있고, server에는 server의 운영 경계가 있어. 공통 규칙 하나로 전부 덮는 순간 어느 쪽에는 불필요한 세금이 붙어.