With the Angular CLI, how does `ng serve` differ from `ng build`, and why is the dev server's output never what you deploy?
answer
- two builders, one build pipeline
- @angular/build:dev-server wraps application
- development vs production configuration
- in memory, watched, served by Vite
- dist/<project>/browser is the artifact
basics
~20 sng serve runs the @angular/build:dev-server builder, which builds the app with its development configuration in memory, watches files and reloads the browser. ng build runs @angular/build:application with the production configuration and writes optimized, hashed files to dist/<project>/browser for deployment.
solid answer
~40 sBoth commands build the application with the same `@angular/build:application` pipeline, but for different purposes. `ng build` (alias `ng b`) runs the `build` target, whose `defaultConfiguration` is `production` in a new project: optimization on, no source maps, `outputHashing: 'all'`, licenses extracted. It writes to `dist/<project>/browser` (plus `server` when SSR is on), and that folder is the deployable artifact. `ng serve` (aliases `ng s`, `ng dev`) runs the `serve` target's `@angular/build:dev-server` builder. It points at `build:development` by default, which turns optimization off and source maps on. It keeps the output in memory, hands it to a Vite-based server, rebuilds only what changed on each save, and reloads or hot-replaces in the browser. It is unminified, unhashed and not written to `dist`, so it is a development tool, never a deployment.
code
json · 16 lines"build": {
"builder": "@angular/build:application",
"defaultConfiguration": "production",
"configurations": {
"production": { "outputHashing": "all" },
"development": { "optimization": false, "extractLicenses": false, "sourceMap": true }
}
},
"serve": {
"builder": "@angular/build:dev-server",
"defaultConfiguration": "development",
"configurations": {
"production": { "buildTarget": "app:build:production" },
"development": { "buildTarget": "app:build:development" }
}
}go deeper
Know that ng serve is for local development with reload on save, and ng build creates the production files in dist to deploy.
Explain the build and serve targets, the dev-server's buildTarget, and exactly which options the production and development configurations change.
Use ng serve --configuration production to reproduce production-only bugs, knowing what changes (HMR off, optimized code), and keep CI deploying only ng build artifacts.
Standardise how every app in the organisation builds and serves, so local, CI and production share one configuration model and differences are deliberate.
## Two targets, two builders In `angular.json` every command maps to a **target** that names a **builder**, the function that does the work. A project created by Angular CLI 22 has: | Target | Builder | `defaultConfiguration` | Used by | |---|---|---|---| | `build` | `@angular/build:application` | `production` | `ng build` / `ng b` | | `serve` | `@angular/build:dev-server` | `development` | `ng serve` / `ng s` / `ng dev` | The dev-server builder does not have its own compiler. Its `buildTarget` option points at the build target with a configuration: `app:build:development` by default, `app:build:production` when you run `ng serve --configuration production`. It then runs that same application build internally. ## What the two configurations change The generated `build` target keeps most values at the builder's schema defaults and overrides a few per configuration: - **production**: `outputHashing: 'all'` and the budgets. Everything else uses the schema defaults, so `optimization` is `true`, `sourceMap` is `false`, `extractLicenses` is `true` and `namedChunks` is `false`. - **development**: `optimization: false`, `extractLicenses: false`, `sourceMap: true`, with `outputHashing` left at its default of `none`. So `ng build` gives minified, tree-shaken, cache-busted files with a separate license file. `ng serve` gives readable code with source maps, which is fast to rebuild and easy to debug. ## What `ng serve` does on top 1. **Builds in memory.** The Angular docs describe the dev server as generating a development build in memory and passing the results to Vite to serve. Vite is used here *only* as a development server and cannot be configured directly. Nothing is written to `dist/`. 2. **Watches and rebuilds incrementally.** `watch` defaults to `true`. On save, only affected files are recompiled. 3. **Updates the browser.** `liveReload` defaults to `true`. Style and template edits are hot-replaced (HMR) where possible; other changes trigger a full page reload. 4. **Prebundles dependencies.** Third-party packages are processed once on first start, so later rebuilds skip them. The `prebundle` option controls it. 5. **Serves on a local address.** By default it serves on `localhost` port `4200`. Host, port, HTTPS and proxying to a backend are dev-server options too. ## Why you never deploy the dev server - The output is **unoptimized**: no minification, no tree-shaking beyond what the bundler does by default, and large source maps. - File names are **not content-hashed** for entry bundles, so browsers and CDNs cannot cache them safely. - It exists only **in the memory of a process** built for fast rebuilds and debugging. It has development-only behaviour such as live reload and prebundling, and it is not designed or hardened to serve real traffic. - `ng build` produces a **static folder** that any web server or CDN can host (or a server bundle for SSR). That is what CI should build and ship. ## Commands you actually type ```bash ng serve # development build, in memory, http://localhost:4200 ng serve --configuration production # check production settings locally ng build # production build into dist/<project>/browser ng build --configuration development # unminified build written to disk ``` `ng serve --configuration production` is handy for reproducing a production-only bug. Note that it serves the production build settings through the dev server, and that HMR turns itself off because of `outputHashing: 'all'`. ## Side by side | Aspect | `ng serve` | `ng build` | |---|---|---| | Builder | `@angular/build:dev-server` | `@angular/build:application` | | Default configuration | `development` | `production` | | Output | In memory | `dist/<project>/browser` (and `server` for SSR) | | Minified | No | Yes | | Source maps | Yes | No | | Hashed entry file names | No | Yes (`outputHashing: 'all'`) | | Watches and reloads | Yes | No, unless `--watch` | | Purpose | Local development | The deployable artifact | The table is a summary of the *generated* defaults. Every row can be changed per configuration, which is why teams sometimes add a `staging` configuration that serves or builds with production-like settings. ## Common interview traps - Saying `ng serve` "uses Webpack". New projects have used the esbuild-based application builder since v17, taken directly from the `@angular/build` package since v20, and the webpack builders in `@angular-devkit/build-angular` are deprecated in v22. - Thinking `ng build` defaults to a development build. In generated projects `defaultConfiguration` is `production`. - Looking for the output in `dist/<project>` directly. The application builder writes browser files to a `browser` subfolder.
- Where does `ng build` put its output, and why did that surprise teams moving from the old builder?By default the application builder writes to `dist/<project>/browser`, plus `dist/<project>/server` when SSR is enabled. The old `browser` builder wrote straight into `dist/<project>`, so deploy scripts that copied that folder broke. Adjust the scripts, or configure `outputPath` so the browser folder name is empty.
- Why might `ng serve` show a brief flash of unstyled content that `ng build` output never shows?The dev server defers processing stylesheets until they are first used, to keep rebuilds fast. The Angular docs note this can cause a small flash of unstyled content on startup. It does not happen in builds outside the dev server.
saying these in an interview costs you the question
- Deploys the output of ng serve or copies files out of the dev server
- Believes ng build produces a development build unless --prod is passed
- Says ng serve in a new project runs a webpack dev server
- Expects the built browser files directly in dist/<project> with the application builder
- Thinks ng serve has its own compiler separate from the application builder