skip to content

With the Angular CLI, how does `ng serve` differ from `ng build`, and why is the dev server's output never what you deploy?

level: juniorimportance: must knowfreq 70%

answer

  1. two builders, one build pipeline
  2. @angular/build:dev-server wraps application
  3. development vs production configuration
  4. in memory, watched, served by Vite
  5. dist/<project>/browser is the artifact

basics

~20 s

ng 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 s

Both 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
json
"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

for a junior

Know that ng serve is for local development with reload on save, and ng build creates the production files in dist to deploy.

for a middle

Explain the build and serve targets, the dev-server's buildTarget, and exactly which options the production and development configurations change.

for a senior

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.

for a principal

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