Bundling compiles your code and all its dependencies into a single file (e.g. main.js). The output is self-contained, so you don't need node_modules or an install step at deploy time.
If your app has native dependencies or you want Docker layer caching for node_modules, see pruning projects for deployment instead.
When to bundle instead of prune
Section titled “When to bundle instead of prune”| Approach | Best for | Trade-off |
|---|---|---|
| Bundling | Serverless functions, simple APIs | Single file output, no node_modules needed |
| Pruning | Docker deployments, native dependencies | Keeps node_modules but only production deps |
Use bundling when:
- Your app has no native dependencies that require OS-level installation
- You want a single deployable artifact with no install step
- You're targeting serverless platforms or lightweight containers
Bundling Node.js applications
Section titled “Bundling Node.js applications”Webpack provides full control over the bundling process. Configure your webpack.config.js to bundle all dependencies.
generatePackageJson: false- Skips generating apackage.jsonsince all dependencies are bundledexternalDependencies: 'none'- Bundles all dependencies instead of treating them as external
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');const { join } = require('path');
module.exports = { output: { path: join(__dirname, 'dist'), }, plugins: [ new NxAppWebpackPlugin({ target: 'node', compiler: 'tsc', main: './src/main.ts', tsConfig: './tsconfig.app.json', outputHashing: 'none', generatePackageJson: false, externalDependencies: 'none', }), ],};esbuild offers faster builds than Webpack. Configure bundling in your project.json:
The bundle: true option tells esbuild to include all dependencies in the output.
{ "name": "my-app", "targets": { "build": { "executor": "@nx/esbuild:esbuild", "options": { "platform": "node", "outputPath": "dist/my-app", "format": ["cjs"], "bundle": true, "main": "apps/my-app/src/main.ts", "tsConfig": "apps/my-app/tsconfig.app.json", "generatePackageJson": false, "esbuildOptions": { "outfile": "dist/my-app/main.js" } } } }}Vite can also bundle Node.js applications with vite by using the build.rollupOptions.external. Set external: [] to bundle all dependencies into the output.
import { defineConfig } from 'vite';
export default defineConfig({ build: { target: 'node18', outDir: 'dist', lib: { entry: 'src/main.ts', formats: ['cjs'], fileName: 'main', }, rollupOptions: { // Bundle all dependencies external: [], }, },});To build and deploy:
nx build my-app# Deploy dist/my-app/main.js - no node_modules neededWhen not to bundle
Section titled “When not to bundle”If you're publishing a library package to npm, avoid bundling dependencies. Declare them in package.json so package managers can handle versioning and deduplication.
For Docker-based deployments, non-bundled builds can improve build times through layer caching. When dependencies don't change, Docker reuses the cached node_modules layer and only rebuilds your application code.
Instead of running build, use the prune target to prepare your application for deployment with its dependencies:
nx prune my-appFor the full setup, see pruning projects for deployment.
This creates a deployment-ready structure:
dist/my-app/├── main.js # Your application code├── package.json # Dependencies manifest└── node_modules/ # Production dependenciesIn your Dockerfile, copy these files and run with Node.js:
COPY dist/my-app /appWORKDIR /appCMD ["node", "main.js"]Bundling libraries
Section titled “Bundling libraries”Publish workspace dependencies as separate packages rather than bundling them into a single library. This gives better versioning control and lets consumers manage their own dependency trees.
Bundling workspace libraries makes sense when:
- The library contains only types that should be inlined
- You want to distribute an internal library as part of your package
Configure the external option to control which dependencies get bundled:
{ "name": "my-lib", "targets": { "build": { "executor": "@nx/esbuild:esbuild", "options": { "platform": "node", "outputPath": "dist/libs/my-lib", "format": ["cjs", "esm"], "bundle": true, "main": "libs/my-lib/src/index.ts", "tsConfig": "libs/my-lib/tsconfig.lib.json", "external": ["^[^./].*$", "!@my-org/utils"] } } }}The external patterns work as follows:
"^[^./].*$"- Externalizes all npm packages (paths not starting with.or/)"!@my-org/utils"- Exception: bundles@my-org/utilsdespite matching the first pattern
See esbuild documentation if using an esbuild configuration file directly
For Vite-based builds, configure the external function in rollupOptions:
import { defineConfig } from 'vite';
export default defineConfig({ build: { lib: { entry: 'src/index.ts', formats: ['es', 'cjs'], }, rollupOptions: { external: (id) => { // Bundle workspace libraries if (id.startsWith('@my-org/')) { return false; } // Externalize npm packages if (!id.startsWith('.') && !id.startsWith('/')) { return true; } return false; }, }, },});The external function receives each import and returns:
falseto bundle the dependencytrueto keep it as an external import
See rollupOptions from vite documentation for more information
Managing workspace dependencies
Section titled “Managing workspace dependencies”When building libraries that consume other workspace libraries, define the dependency relationship in package.json:
{ "name": "@my-org/my-lib", "dependencies": { "@my-org/utils": "workspace:*" }}The workspace:* syntax:
- During development: resolves to the local workspace package
- During publish: gets replaced with the actual version number
This ensures your library correctly declares its dependencies regardless of whether they're bundled.
Quick reference
Section titled “Quick reference”| Scenario | Tool | Key Settings |
|---|---|---|
| Bundled Node app | Webpack | generatePackageJson: false, externalDependencies: 'none' |
| Bundled Node app | esbuild | bundle: true, generatePackageJson: false |
| Bundled Node app | Vite | external: [] in rollupOptions |
| Non-bundled Node app | Any | Run prune target, deploy with node_modules |
| Publishable lib (bundle workspace deps) | esbuild | bundle: true, external: ["^[^./].*$", "!@org/lib"] |
| Publishable lib (bundle workspace deps) | Vite | Custom external function in rollupOptions |