An Angular CLI app deployed under `/shop/` loads a blank page and its deep links 404; how do `baseHref`, `deployUrl` and the server config each factor in?
answer
- where relative URLs resolve
- trailing slash matters
- deployUrl is build-time and narrow
- the server must fall back
basics
~10 sBuild with baseHref /shop/ so index.html gets <base href="/shop/"> and scripts and routes resolve under it, and configure the server to return /shop/index.html for unknown paths. deployUrl only prefixes asset URLs, for CDN-style setups.
solid answer
~40 sA blank page usually means the scripts in `index.html` resolved against `/` and 404ed: the app was built with the default `<base href="/">`. Set `baseHref: "/shop/"` in the build configuration (or `ng build --base-href /shop/`); the builder writes it into `<base href>`, the browser resolves relative script and style URLs against it, and the router uses it as the app's root. The trailing slash matters, since `/shop` resolves relative files against `/`. Deep links like `/shop/orders/42` then need the web server to return `/shop/index.html` for paths it has no file for. `deployUrl` is a different tool: it prefixes relative script and style URLs at build time, for serving assets from another location such as a CDN; the Angular docs say to prefer `<base href>`. In development, `ng serve` derives its serve path from `baseHref`.
code
bash · 2 linesng build --base-href /shop/
grep -o '<base href="[^"]*">' dist/shop/browser/index.htmlgo deeper
Recall that the base href must match the path the app lives under, with a trailing slash, and that the server needs an index.html fallback.
Explain what reads the base element, what deployUrl prefixes and when, and how ng serve derives its serve path.
Diagnose from symptoms: blank page versus deep-link 404 versus wrong router links, and fix build config and server config separately.
Weigh sub-path hosting behind a shared domain against a dedicated host: routing ownership, asset caching and coordination with platform teams.
## Two different problems A sub-path deployment tends to fail in two separate ways, and each has its own fix: | Symptom | Cause | Fix | |---|---|---| | Blank page, script 404s in the network tab | scripts resolved against `/`, not `/shop/` | build with `baseHref: "/shop/"` | | `/shop/` works, reloading `/shop/orders/42` gives 404 | the web server has no such file | server fallback to `/shop/index.html` | | Routes render but links jump to `/orders` | router base still `/` | same `baseHref` fix | ## `baseHref`: where the app lives The `@angular/build:application` builder writes `baseHref` into the `<base href>` element of `index.html`, replacing an existing one or inserting it. Two things read that element: 1. **The browser**, which resolves every relative URL in the document against it, including the generated `<script src="main-XXXX.js">` and stylesheet links. 2. **The Angular router**, whose default `PathLocationStrategy` treats it as the application's root, so `routerLink="/orders"` produces `/shop/orders`. Set it in the configuration you deploy, or per build on the command line: ```json { "configurations": { "production": { "baseHref": "/shop/" } } } ``` The **trailing slash** matters. With `/shop`, the browser treats `shop` as a file name and resolves `main-XXXX.js` against `/`, which reproduces the blank page. Angular's `APP_BASE_HREF` documentation likewise says the base should end with `/`. `APP_BASE_HREF` itself is the runtime alternative: a provider that tells the router's `PathLocationStrategy` its base without reading the DOM element. For the static script URLs in `index.html`, the build-time `baseHref` is what counts. ## The server fallback Client-side routes do not exist as files. When a user reloads `/shop/orders/42`, the browser asks the web server for that path, bypassing the router. The Angular deployment guide's rule applies unchanged under a sub-path: configure the server to return the app's host page, here `/shop/index.html`, for requests it has no file for, while real 404s inside the app are shown by the router's wildcard route. With `outputMode: "server"` the generated Node server handles this itself, as long as the reverse proxy in front of it forwards `/shop/` requests to it. ## `deployUrl`: a narrower, build-time tool `deployUrl` prefixes **relative** URLs of scripts and stylesheets in `index.html`, and resources in component styles, with a fixed string at build time; root-relative and absolute URLs are left alone. The builder schema says it is "only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations". - It does **not** change the router's base, so it cannot fix deep links. - It is **baked in** at build time, while `<base href>` sits in one place in `index.html` that a deployment can still rewrite. - The Angular deployment guide: "Prefer `<base href>` where possible." Reach for it when the HTML is served from one place and the bundles from another. ## Development parity `ng serve` reads the same `baseHref`: if the serve target's `servePath` is not set, the dev server derives it from the build's `baseHref`, so the app is served at `http://localhost:4200/shop/`. Testing locally under the same path catches missing trailing slashes before deployment. Root-relative asset paths in templates, such as `/logo.svg`, ignore the base and break under a sub-path; relative ones keep working. ## Verifying before release 1. Build with the deployed configuration and read the `<base href>` in `dist/<project>/browser/index.html`. 2. Serve the output under the same sub-path locally, or run `ng serve` with that configuration, and watch the network panel for requests that escape to `/`. 3. Reload a deep link such as `/shop/orders/42`; a 404 from the server rather than the app points at the missing fallback. 4. Click through router links and confirm they stay under `/shop/`. Most sub-path bugs are found in the first two minutes of this check, and all of them are cheaper to find here than after the deployment. ## Checklist for a sub-path deployment - Set `baseHref` with a trailing slash in the deployed configuration. - Configure the web server fallback to the sub-path's `index.html`. - Keep asset references relative in templates and styles. - Use `deployUrl` only when bundles are hosted somewhere other than the HTML.
- Why does `--base-href /shop` without the trailing slash still break the page?The browser treats the last path segment of a base URL without a trailing slash as a file name, so relative URLs like `main-XXXX.js` resolve against `/`, not `/shop/`. The scripts 404 exactly as if no base were set. Always write `/shop/`.
- When is `deployUrl` the right tool rather than `baseHref`?When `index.html` is served from one location but the bundles live elsewhere, for example on a separate asset host, or when an Angular Elements bundle is embedded in pages the app does not own. It only prefixes relative script and style URLs and does not move the router's base, so it cannot fix deep-link 404s.
- Does `ng serve` need a separate setting to test the `/shop/` path locally?Usually not. When the serve target's `servePath` is unset, the dev server derives it from the build's `baseHref`, so a development configuration with `baseHref: "/shop/"` is served at `http://localhost:4200/shop/`. Set `servePath` only when the two must differ.
saying these in an interview costs you the question
- deployUrl moves the router's base, so it fixes deep links too.
- A base href of /shop without a trailing slash is equivalent.
- Deep-link 404s are fixed by adding more routes in the app.
- baseHref only affects the router, not script URLs.
- Root-relative image paths like /logo.svg work under any sub-path.