Skip to content
Angular ng deployment 4 min read

Deploying to Static Hosts

A client-rendered Angular application is, after the production build, just a folder of static files: an index.html, hashed JavaScript bundles, CSS, and assets. That makes static hosts like Netlify, Vercel, Firebase Hosting, and GitHub Pages an excellent, low-cost, low-maintenance home for single-page apps. The one detail you must get right on every host is SPA fallback routing — telling the host to serve index.html for any unmatched path so the Angular router can take over on the client.

How a static Angular build works

Running the production build emits a self-contained bundle into the output directory. With the application builder (the default since Angular 17), client assets land in dist/<project>/browser.

ng build

Output:

Initial chunk files | Names         |  Raw size | Estimated transfer size
main-7QZ4FK2A.js    | main          | 210.44 kB |                58.12 kB
styles-5INURTSO.css | styles        |  18.30 kB |                 3.10 kB

Output location: dist/my-app/browser
Application bundle generation complete. [4.812 seconds]

The browser folder is what you upload. Everything in it is fingerprinted, so it can be cached aggressively — except index.html, which must always be served fresh so clients pick up new bundle hashes.

If you are setting a non-root deployment path (for example a GitHub Pages project site), build with --base-href so generated asset URLs resolve correctly: ng build --base-href "/my-repo/".

Why SPA fallback routing matters

The Angular router uses the HTML5 History API, producing clean URLs like /products/42. When a user navigates inside the app that works fine, but if they reload that URL or open it directly, the host receives a request for /products/42 — a path that has no corresponding file. Without a fallback rule, the host returns a 404.

The fix is universal: rewrite every unmatched request to /index.html with a 200 status. Angular boots, reads the URL, and renders the right route. Each host expresses this rule differently.

Netlify

Add a _redirects file to the build output, or a netlify.toml at the repo root.

[build]
  command = "ng build"
  publish = "dist/my-app/browser"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

The status = 200 (not 301/302) is what makes it a rewrite rather than a redirect, preserving the original URL in the address bar.

Vercel

Vercel auto-detects Angular, but you can pin the configuration with vercel.json.

{
  "buildCommand": "ng build",
  "outputDirectory": "dist/my-app/browser",
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}

Firebase Hosting

Use the Angular CLI deploy integration, which wires up Firebase end to end.

ng add @angular/fire
ng deploy

That generates a firebase.json. The critical block is the rewrite:

{
  "hosting": {
    "public": "dist/my-app/browser",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [{ "source": "**", "destination": "/index.html" }]
  }
}

GitHub Pages

GitHub Pages has no rewrite engine, so the common trick is a 404.html that mirrors index.html. The angular-cli-ghpages package automates the build, the base href, and the 404 copy.

ng add angular-cli-ghpages
ng deploy --base-href=/my-repo/

Output:

📦 Building "my-app"
🔧 Building application bundle...
🌐 Deploying to gh-pages branch...
🚀 Successfully published via angular-cli-ghpages!

Host comparison

HostFallback mechanismCustom domainBuild integration
Netlify_redirects / netlify.toml 200Yes (free TLS)Git auto-deploy
Vercelrewrites in vercel.jsonYes (free TLS)Git auto-deploy
Firebaserewrites in firebase.jsonYes (free TLS)ng deploy
GitHub Pages404.html copy of index.htmlYes (free TLS)angular-cli-ghpages

Caching headers

Because bundle filenames are content-hashed, serve them with a long, immutable cache while keeping index.html uncached. On Netlify, add a _headers file:

/index.html
  Cache-Control: no-cache
/*.js
  Cache-Control: public, max-age=31536000, immutable
/*.css
  Cache-Control: public, max-age=31536000, immutable

A stale index.html is the most common “users see the old app after deploy” bug. Always send no-cache (revalidate) for index.html so the browser re-fetches the latest bundle hashes.

Best Practices

  • Publish the browser subfolder of the dist output, not the parent dist directory.
  • Configure the SPA fallback rewrite with a 200 status so the URL is preserved and the router can resolve it.
  • Set --base-href whenever the app is served from a subpath, and verify asset URLs in the deployed index.html.
  • Cache hashed bundles for a year as immutable, but serve index.html with no-cache.
  • Prefer Git-connected auto-deploys (Netlify/Vercel) or ng deploy (Firebase) over manual uploads to keep deployments reproducible.
  • Remember that static hosting serves client-rendered HTML only; if you need server rendering for SEO or first-paint, deploy an SSR target instead.
Last updated June 14, 2026
Was this helpful?