An Angular app still builds with `@angular-devkit/build-angular:browser`; how do you migrate it to the application builder, and what breaks along the way?
answer
- ng update @angular/cli --name use-application-builder
- main becomes browser, polyfills an array
- removed webpack-era options
- dist/<project>/browser output folder
- custom webpack configs do not migrate
basics
~10 sRun ng update @angular/cli --name use-application-builder: it switches to the application builder, renames main to browser and drops webpack-only options. Then fix the new dist/<project>/browser output path, non-ESM imports and any custom webpack setup.
solid answer
~40 sThe supported route is the optional migration `ng update @angular/cli --name use-application-builder`, which Angular offers during updates and which is still listed for v22. For each application whose build target uses `browser` or `browser-esbuild` it sets the application builder, and it renames `main` to `browser` and `ngswConfigPath` to `serviceWorker`. It wraps a string `polyfills` in an array, moves `resourcesOutputPath` into `outputPath.media`, and deletes `buildOptimizer`, `vendorChunk` and `commonChunk`. It also removes the old `server`, `prerender`, `app-shell` and `ssr-dev-server` targets in favour of options on the one builder, merges the server tsconfig, strips `~` and `^` from Sass imports, and switches to `@angular/build` when nothing else needs `@angular-devkit/build-angular`. What breaks: deploy scripts expecting `dist/<project>` instead of `dist/<project>/browser`, `import * as x` of CommonJS packages, and projects on third-party webpack builders, which the migration refuses to convert.
code
json · 10 lines"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"outputPath": { "base": "dist/shop", "browser": "" },
"serviceWorker": "ngsw-config.json",
"tsConfig": "tsconfig.app.json"
}
}go deeper
Know that older Angular apps use a webpack-based browser builder and that newer ones use the faster application builder.
Run the use-application-builder migration and explain the main option changes: main to browser, polyfills array, removed webpack-only options.
Own the migration end to end: output path and deploy changes, ESM import fixes, SSR server code, custom webpack replacements, and a verification plan.
Sequence the builder migration across many apps alongside Angular upgrades, deciding when deprecated webpack customisations must be removed rather than ported.
## Why migrate The `browser` builder in `@angular-devkit/build-angular` is webpack-based. New projects have used the esbuild-based **application builder** since v17, and in v22 the webpack builders in `@angular-devkit/build-angular` (and `@ngtools/webpack`) are deprecated. The application builder is much faster, builds browser and server code in one step, and is where new features land. Staying on `browser` means slow builds and a dead end. ## Step 1: run the automated migration ```bash ng update @angular/cli --name use-application-builder ``` It is marked optional and recommended in `@schematics/angular`'s migration collection, so an interactive `ng update` offers it pre-selected. Running it by name works at any time after an update. Commit first and review the diff. ## What the migration changes For every **application** project whose `build` target uses `browser` or `browser-esbuild`: | Old `browser` option | After migration | |---|---| | `builder: ...:browser` | application builder (`@angular/build:application` when possible) | | `main` | `browser` | | `polyfills: "src/polyfills.ts"` | `polyfills: ["src/polyfills.ts"]` | | `ngswConfigPath` | `serviceWorker` | | `resourcesOutputPath` | `outputPath.media` (only if not `media`) | | `outputPath: "dist/app"` | `outputPath: { "base": "dist/app" }`, so files land in `dist/app/browser` | | `buildOptimizer`, `vendorChunk`, `commonChunk` | deleted (covered by `optimization` or no longer needed) | It also: 1. deletes the separate `server`, `prerender`, `app-shell` and `ssr-dev-server` targets, since the application builder does all of that itself, and runs the `ssr` schematic when a server entry existed; 2. merges `tsconfig.server.json` into `tsconfig.app.json`; 3. rewrites webpack-specific stylesheet syntax, the `~` and `^` prefixes in `@import` and `url()`, and adds include paths so they still resolve; 4. replaces `@angular-devkit/build-angular` with the smaller `@angular/build` package when no other target still needs the former, retargeting `dev-server`, `extract-i18n`, `karma` and `ng-packagr` too; 5. adds `less` or `postcss` dev dependencies if the project uses Less files or a PostCSS config. ## What still needs a human - **Output location.** The migration logs a warning that output moved to `dist/<project>/browser`. Update deploy scripts, container-image copy steps and server static roots, or set `outputPath.browser` to `""` to keep the old layout. - **Custom webpack.** Projects using a third-party builder that accepts a webpack config are *not* migrated. The migration logs that only `browser` and `browser-esbuild` can be converted automatically. Each webpack customisation needs an equivalent: `define` for build-time constants, `loader` for importing files as text or binary, `externalDependencies`, or dropping it. - **ESM strictness.** esbuild follows the ECMAScript spec. `import * as moment from 'moment'` followed by `moment()` builds with a warning and crashes at runtime. Enable `esModuleInterop` and use a default import. - **CommonJS in SSR server code.** `require`, `__dirname` and `__filename` must go; server code must be ESM. - **Order-dependent side-effect imports** shared across lazy chunks can run out of order, a known issue. Prefer modules without global side effects. - **Web Workers.** Worker code is not type-checked and nested workers are not processed. - **Karma.** If tests still run on Karma, they keep working. Moving to the unit-test builder is a separate migration. ## What does not need changing - **`ng serve`.** The dev server detects the application builder automatically. The `serve` target and its command-line options stay the same. - **Most application code.** Components, templates and services compile the same way. The problems cluster around build configuration, imports and deployment paths. - **SSR scripts.** Separate `ng run app:server` or prerender steps in npm scripts can usually be dropped, because `ng build` now produces the server output and prerendered pages itself. ## A safe rollout 1. Update Angular first, one major per step, so you run the migration shipped with your target CLI version. 2. Run the migration on a branch, build, and fix every warning, since the new build reports problems the old one tolerated. 3. Compare output: bundle sizes against budgets, `index.html`, the asset list and the deploy folder layout. 4. Smoke-test the running app, especially lazy routes, third-party widgets and anything that used webpack magic comments or `require.context`. 5. Update CI caching and deployment paths in the same change.
- The migration finished but the deploy published an empty site. What happened?The application builder writes browser files to `dist/<project>/browser`, not `dist/<project>`. The deploy step copied the parent folder, or looked for `index.html` in the old place. Point the deploy at the `browser` subfolder, or set `outputPath: { "base": "dist/<project>", "browser": "" }` to restore the old layout.
- Your project uses a third-party builder with a custom webpack config. What are your options?The migration will not convert it. List what the webpack config does and map each item: constants become `define`, file imports become `loader` or import attributes, and some items become `externalDependencies` or code changes. Items with no equivalent need a rethink. Only once nothing depends on webpack, switch the builder by hand, following the manual migration list.
- Should you switch to `browser-esbuild` instead?`browser-esbuild` was a compatibility builder with the old option names and most of the speed gain. It lives in the deprecated `@angular-devkit/build-angular` package and the migration converts it too. Today it makes sense at most as a short stepping stone; the destination is the application builder.
saying these in an interview costs you the question
- Changes only the builder name and expects the old options to keep working
- Assumes output still lands in dist/<project> after migrating
- Expects the migration to translate a custom webpack config automatically
- Keeps import * as x from a CommonJS package and calls x() directly
- Treats browser-esbuild as the long-term target in v22