Serverless functions reward small, predictable deployment packages. Every unnecessary dependency increases upload time, cold-start work, vulnerability surface, and the amount of code your runtime must initialise. For teams building APIs, webhooks, scheduled jobs, and AI features, choosing a lightweight JavaScript bundler for serverless functions is therefore an operational decision—not just a build-tool preference.
A good bundler should produce a portable artifact, preserve the runtime’s module behaviour, remove code you do not use, and fit cleanly into CI/CD. This matters whether you deploy to AWS Lambda, Google Cloud Functions, Azure Functions, Cloudflare Workers, or a platform used by an Indian startup seeking predictable costs and simple operations. If your function calls an external model or API, the same discipline complements broader guidance on building serverless AI apps with Modal and deploying serverless AI models with GitHub.
What to optimise in a serverless bundle
A browser bundle is designed around network delivery and user interaction. A serverless bundle has different constraints:
- Cold-start path: The runtime must load your entry point and its imports before handling a request.
- Deployment size: Large archives take longer to upload, unpack, scan, and distribute.
- Module compatibility: Your output must match the platform’s Node.js version and CommonJS or ESM expectations.
- Native dependencies: Packages with binaries may need platform-specific builds and cannot always be safely bundled.
- Debuggability: Source maps and readable stack traces matter when failures occur only in production.
- Repeatability: A lockfile, deterministic build, and pinned runtime reduce “works locally” failures.
Bundle size is not the only performance metric. A package can be small but slow if it performs expensive module initialisation, opens connections at import time, or includes a large SDK for a single API call. Measure both the compressed artifact and the time spent during initialisation.
Best bundlers for serverless JavaScript
esbuild: the practical default
esbuild is usually the strongest starting point for Node.js functions. It is fast, supports TypeScript and modern JavaScript, performs tree-shaking, and needs relatively little configuration. This makes it well suited to pull requests and CI runners where build speed affects developer productivity.
A typical command might look like this:
esbuild src/handler.ts \
--bundle \
--platform=node \
--target=node22 \
--format=esm \
--outfile=dist/handler.mjs \
--sourcemapAdjust target and format to your provider’s supported runtime. Do not blindly copy a Node version from a local machine: verify the deployed runtime first. Mark provider-supplied packages as external when appropriate, and test the final archive rather than assuming the build output is valid.
Rollup: best when output control matters
Rollup remains valuable for libraries, shared packages, and projects that need precise output structure. Its mature plugin ecosystem and strong ES module tree-shaking can produce compact artifacts, but configuration is more involved than with esbuild. Choose it when you need custom transforms, multiple entry points, or a carefully controlled shared package—not merely because it is popular for frontend libraries.
tsup: a convenient TypeScript wrapper
For TypeScript-heavy codebases, tsup provides a developer-friendly configuration layer around esbuild. It can simplify declaration files, multiple formats, and clean builds for internal packages. It is particularly useful when several functions share a monorepo package, though you should still inspect each function’s final dependency graph.
Vite and Parcel: useful, but not usually the first choice
Vite and Parcel are excellent application build tools, especially for frontend-heavy projects. Vite’s development server and plugin model are valuable when a repository contains both a web interface and serverless endpoints. However, for a small function, their application-oriented defaults can add complexity. Use them when you need their ecosystem; otherwise, a direct esbuild or tsup pipeline is usually easier to audit.
A reliable build configuration
Start with one entry point per function and emit a separate directory for each handler. A minimal esbuild script could be:
import { build } from "esbuild";
await build({
entryPoints: ["src/handler.ts"],
bundle: true,
platform: "node",
target: "node22",
format: "esm",
outdir: "dist",
sourcemap: "external",
minify: true,
packages: "external"
});packages: "external" is deliberately conservative: it keeps npm packages outside the bundle, so your deployment process must install or copy them. For many serverless functions, bundling dependencies is preferable. Instead, list only known platform-provided or intentionally shared packages in external, then verify the archive in a clean container. The correct setting depends on the hosting provider and packaging workflow.
Use conditional exports carefully. Some dependencies expose different browser, ESM, and Node entry points, and an incorrect condition can create runtime-only errors. Avoid importing browser-only SDKs into a backend handler. For AI applications, prefer a focused client package over an entire multi-service SDK where possible; this is the same principle used when building lightweight AI web tools.
Dependency and cold-start checklist
Before selecting a bundler, reduce what it has to process:
- Replace large utility libraries with native APIs or narrowly scoped packages.
- Import individual functions rather than entire namespaces where tree-shaking supports it.
- Keep database clients and cloud SDKs outside the hot path when they are not needed by every request.
- Move rarely used features into separate functions instead of one conditional mega-handler.
- Avoid top-level network requests, model loading, and expensive parsing.
- Reuse connections between invocations, but initialise them lazily and safely.
- Remove test fixtures, documentation, source files, and local credentials from the deployment archive.
- Run
npm auditor an equivalent scanner, but review findings rather than adding unnecessary production packages to silence them.
For functions that support ML inference, bundle only the client and preprocessing code required by the endpoint. Heavy model files usually belong in object storage, a managed inference service, or a separately optimised runtime. Teams exploring edge deployment should also compare this approach with deploying lightweight machine learning models on the edge.
Test the artifact, not just the source
A successful local test does not prove that the deployed package works. Add an artifact test to CI:
1. Delete node_modules and build from a clean checkout.
2. Create the exact zip, image, or worker bundle used in deployment.
3. Run the handler in a container matching the production runtime.
4. Test ESM/CJS loading, environment variables, native modules, and source-map stack traces.
5. Record uncompressed size, compressed size, build time, and initialisation latency.
6. Fail the build when size or dependency thresholds are exceeded without review.
Also test from a network location representative of your users. For Indian products, latency to the selected cloud region, egress charges, and data-residency requirements can matter as much as bundling. A tiny bundle deployed far from its users may still deliver a poor experience; compare these trade-offs with the region and platform considerations in best serverless hosting for Indian AI startups.
Which bundler should you choose?
- Choose esbuild for most individual Node.js or TypeScript functions.
- Choose tsup when you want esbuild’s speed with simpler TypeScript package configuration.
- Choose Rollup for libraries, custom plugins, and precise module output.
- Choose Vite when serverless endpoints are part of a frontend application that already depends on Vite.
- Choose Parcel when zero-configuration application builds outweigh minimal function-specific control.
The best choice is the one that produces a small, reproducible artifact and remains understandable six months later. Start with esbuild, inspect the output, and change tools only when a measured requirement justifies it. For developers building resource-constrained AI products, the same principle applies to model selection: compare practical footprint and runtime behaviour, as discussed in best lightweight AI models for Indian app developers.
FAQ
Does bundling always reduce cold starts?
No. It can reduce file loading and module resolution, but initialisation code, native libraries, network calls, and runtime configuration may dominate. Benchmark the deployed artifact.
Should dependencies be bundled or installed during deployment?
Bundle pure JavaScript dependencies when it simplifies deployment and reduces file overhead. Keep native modules or provider-managed packages external only when the platform supports them and the build matches its runtime.
Is minification safe for backend functions?
Usually, but test error reporting, reflection-heavy libraries, dynamic imports, and packages that depend on function names. Keep external source maps available for production debugging.
Do I need a bundler for a small function?
Not always. A single-file function with one dependency may deploy directly. Once you use TypeScript, multiple modules, shared packages, or several dependencies, a repeatable bundler pipeline typically pays for itself.