Skip to content

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.

ApproachBest forTrade-off
BundlingServerless functions, simple APIsSingle file output, no node_modules needed
PruningDocker deployments, native dependenciesKeeps 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

Webpack provides full control over the bundling process. Configure your webpack.config.js to bundle all dependencies.

  • generatePackageJson: false - Skips generating a package.json since all dependencies are bundled
  • externalDependencies: 'none' - Bundles all dependencies instead of treating them as external
webpack.config.js
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',
}),
],
};

To build and deploy:

Terminal window
nx build my-app
# Deploy dist/my-app/main.js - no node_modules needed

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:

Terminal window
nx prune my-app

For 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 dependencies

In your Dockerfile, copy these files and run with Node.js:

COPY dist/my-app /app
WORKDIR /app
CMD ["node", "main.js"]

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:

project.json
{
"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/utils despite matching the first pattern

See esbuild documentation if using an esbuild configuration file directly

When building libraries that consume other workspace libraries, define the dependency relationship in package.json:

libs/my-lib/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.

ScenarioToolKey Settings
Bundled Node appWebpackgeneratePackageJson: false, externalDependencies: 'none'
Bundled Node appesbuildbundle: true, generatePackageJson: false
Bundled Node appViteexternal: [] in rollupOptions
Non-bundled Node appAnyRun prune target, deploy with node_modules
Publishable lib (bundle workspace deps)esbuildbundle: true, external: ["^[^./].*$", "!@org/lib"]
Publishable lib (bundle workspace deps)ViteCustom external function in rollupOptions