How do you configure Angular routing on a production web server?
Angular normally uses client-side routing. In production, the web server must be configured so that Angular routes such as:
/products
/articles
/admin/dashboard
return Angular's index.html instead of producing a server-side 404 Not Found.
The core rule is:
If the requested URL is not a real static file or backend/API endpoint, return
index.htmland let Angular Router handle the route.
1. What is the routing problem in production?
Suppose your Angular routes are:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
loadComponent: () =>
import('./home').then(m => m.Home)
},
{
path: 'products',
loadComponent: () =>
import('./products').then(m => m.Products)
},
{
path: 'contact',
loadComponent: () =>
import('./contact').then(m => m.Contact)
}
];
Angular understands:
/
/products
/contact
When navigating inside Angular, everything works:
Home
↓
click Products
↓
Angular Router
↓
/products
↓
Products component
No full page request may be required.
2. The refresh/direct-navigation problem
Now directly enter:
https://www.example.com/products
or refresh the browser while on /products.
The browser sends:
GET /products
to the production web server.
The server may think:
Does /products exist as a physical file/folder?
↓
No
↓
404 Not Found
But /products isn't supposed to be a physical file.
It is an Angular route.
Therefore, the server needs to return:
index.html
Then Angular starts and Angular Router reads /products.
3. Correct production flow
The desired flow is:
Browser
|
| GET /products
↓
Web Server
|
| Is /products a physical file?
| No
↓
Return index.html
↓
Angular starts
↓
Angular Router examines URL
↓
/products
↓
Products component
This is generally called an SPA fallback or URL rewrite/fallback.
4. Full Simple Angular Example
app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
loadComponent: () =>
import('./home').then(m => m.Home)
},
{
path: 'products',
loadComponent: () =>
import('./products').then(m => m.Products)
},
{
path: 'contact',
loadComponent: () =>
import('./contact').then(m => m.Contact)
},
{
path: '**',
loadComponent: () =>
import('./not-found').then(m => m.NotFound)
}
];
app.config.ts
import {
ApplicationConfig
} from '@angular/core';
import {
provideRouter
} from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes)
]
};
main.ts
import {
bootstrapApplication
} from '@angular/platform-browser';
import { App } from './app/app';
import { appConfig } from './app/app.config';
bootstrapApplication(App, appConfig)
.catch(err => console.error(err));
5. Build Angular
Create the production build:
ng build --configuration production
You may get output similar to:
dist/
└── my-app/
└── browser/
├── index.html
├── main-XXXX.js
├── styles-XXXX.css
└── chunk-XXXX.js
Deploy these browser files to your web server.
But deployment alone isn't enough.
You also need the SPA fallback configuration.
6. ASP.NET Core hosting
If ASP.NET Core is serving your Angular static files, this is straightforward.
Suppose:
wwwroot/
├── index.html
├── main-XXXX.js
├── styles-XXXX.css
└── chunk-XXXX.js
Configure:
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthorization();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.Run();
The critical line is:
app.MapFallbackToFile("index.html");
7. What does MapFallbackToFile() do?
Suppose the browser requests:
/products
ASP.NET Core checks the mapped endpoints.
There isn't an API/controller endpoint for:
/products
The fallback returns:
index.html
Then Angular Router processes:
/products
Flow:
GET /products
↓
ASP.NET Core
↓
No matching endpoint
↓
Fallback
↓
index.html
↓
Angular starts
↓
Angular Router
↓
Products
8. What about ASP.NET Core API routes?
Suppose your API is:
/api/products
Controller:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok(new[]
{
new { Id = 1, Name = "Laptop" },
new { Id = 2, Name = "Mouse" }
});
}
}
Then:
GET /api/products
matches:
ProductsController
It doesn't need Angular's fallback.
Conceptually:
Request
|
├── /api/products
│ ↓
│ ASP.NET Core Controller
│
├── /main-ABC.js
│ ↓
│ Static File
│
└── /products
↓
index.html
↓
Angular Router
This separation is important.
9. IIS hosting
If Angular is hosted directly in IIS rather than being served by ASP.NET Core, IIS needs a URL rewrite/fallback rule.
A common web.config pattern is:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Angular Routes"
stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}"
matchType="IsFile"
negate="true" />
<add input="{REQUEST_FILENAME}"
matchType="IsDirectory"
negate="true" />
</conditions>
<action type="Rewrite"
url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
The concept is:
Request /products
↓
Is it a physical file?
↓ No
Is it a physical directory?
↓ No
Rewrite to Angular entry page
↓
Angular Router
↓
/products
This typically requires the IIS URL Rewrite functionality to be available.
10. Why check IsFile?
Suppose Angular requests:
/main-ABC123.js
That is a real file.
You don't want:
/main-ABC123.js
↓
index.html
You want:
/main-ABC123.js
↓
main-ABC123.js
Therefore:
<add input="{REQUEST_FILENAME}"
matchType="IsFile"
negate="true" />
essentially means:
Apply the SPA rewrite only when the requested path is not an existing file.
11. Why check IsDirectory?
Similarly, existing directories shouldn't unnecessarily be rewritten.
<add input="{REQUEST_FILENAME}"
matchType="IsDirectory"
negate="true" />
means:
Apply the fallback when the requested path isn't an existing directory.
12. Nginx hosting
For Nginx, a common SPA configuration uses try_files.
Conceptually:
server {
listen 80;
server_name www.example.com;
root /var/www/angular/browser;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
The important line is:
try_files $uri $uri/ /index.html;
It means:
Request /products
↓
Does /products exist?
↓ No
Return /index.html
↓
Angular Router
But:
Request /main-ABC.js
↓
Does file exist?
↓ Yes
Return main-ABC.js
13. Apache hosting
For Apache, SPA fallback is commonly implemented using rewrite rules in .htaccess or the virtual-host configuration.
Conceptually:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]
Meaning:
Not physical file
AND
Not physical directory
↓
index.html
14. Angular route vs server route
This distinction is extremely important.
Suppose:
/products
This may be an Angular route.
/api/products
This may be an ASP.NET Core API route.
Therefore:
https://www.example.com/products
↑
Angular Router
https://www.example.com/api/products
↑
ASP.NET Core
The production server needs to distinguish these correctly.
15. Angular wildcard route vs server fallback
These are related but not the same thing.
Angular:
{
path: '**',
loadComponent: () =>
import('./not-found').then(m => m.NotFound)
}
handles unknown routes after Angular has loaded.
For example:
/abcdef
↓
Web Server
↓
index.html
↓
Angular
↓
**
↓
NotFound component
But the server must first return index.html.
Therefore:
Server SPA fallback
↓
Gets request into Angular
Angular wildcard **
↓
Handles unknown Angular route
You may need both.
16. What is <base href="/">?
Angular's index.html normally contains:
<base href="/">
This tells Angular/browser URL resolution that the application is hosted from the root path.
For example:
https://www.example.com/
Routes:
/products
/articles
/contact
work relative to:
/
17. What if Angular is hosted in a subfolder?
Suppose your application is deployed at:
https://www.example.com/portfolio/
rather than:
https://www.example.com/
Then the application's base path needs to correspond to that deployment.
For example:
<base href="/portfolio/">
You can specify a base href when building:
ng build --configuration production --base-href /portfolio/
Then routes can look like:
https://www.example.com/portfolio/
https://www.example.com/portfolio/products
https://www.example.com/portfolio/articles
The server fallback also needs to point to the correct application location.
18. Hash-based routing alternative
Angular can also use hash-based routing.
For example:
https://www.example.com/#/products
Instead of:
https://www.example.com/products
With standalone router configuration, this can be enabled using:
import {
provideRouter,
withHashLocation
} from '@angular/router';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withHashLocation()
)
]
};
Now:
https://www.example.com/#/products
The part after # isn't sent to the server as the request path in the same way.
Therefore, server-side SPA fallback configuration is less of an issue.
However, normal path-based URLs are commonly preferred:
/products
rather than:
/#/products
when the web server can be configured properly.
19. Development vs Production
During development:
ng serve
Angular's development server handles SPA routing for you.
So:
http://localhost:4200/products
generally works.
That can create confusion:
Development
/products
↓
Works
Production
/products
↓
Refresh
↓
404
The Angular route itself isn't necessarily wrong.
The missing piece may be the production server's SPA fallback configuration.
20. Full Production Flow
Suppose the user enters:
https://www.example.com/articles/10
The complete flow is:
Browser
↓
GET /articles/10
↓
Production Web Server
↓
Does physical file exist?
↓
No
↓
SPA fallback
↓
index.html
↓
Angular bootstraps
↓
Angular Router reads
/articles/10
↓
Article Component
↓
Angular calls
/api/articles/10
↓
ASP.NET Core Web API
↓
Database
↓
JSON
↓
Angular displays article
Notice the two different routes:
/articles/10
↑
Angular route
/api/articles/10
↑
ASP.NET Core route
Production Server Configuration Summary
| Hosting | Typical SPA fallback |
|---|---|
| ASP.NET Core | MapFallbackToFile("index.html") |
| IIS | URL Rewrite rule |
| Nginx | try_files ... /index.html |
| Apache | Rewrite to index.html |
| Static/cloud hosting | Configure platform-specific SPA fallback/rewrite |
Regardless of server technology, the principle is the same:
Unknown client route
↓
index.html
↓
Angular Router
Key Points
- Angular Router is primarily client-side routing.
- Directly opening or refreshing
/productssends/productsto the production server. - The production server must provide an SPA fallback.
- The fallback usually returns:
index.html
- With ASP.NET Core:
app.MapFallbackToFile("index.html");
- With Nginx, the common concept is:
try_files $uri $uri/ /index.html;
- With IIS, configure an appropriate URL Rewrite rule.
- Existing static files must not be rewritten to
index.html. - Backend routes such as
/api/productsshould continue to reach ASP.NET Core. - Angular's
**wildcard route does not replace the server-side SPA fallback. <base href="/">is important for root deployment.- Subfolder deployments require the correct base path and corresponding server configuration.
Interview Questions and Answers
1. How do you configure Angular routing on a production server?
Configure the web server with an SPA fallback so that routes that aren't physical files or backend endpoints return Angular's index.html. Angular Router then handles the client-side URL.
2. Why does an Angular route work when clicking a link but fail after refresh?
Client-side navigation is handled by Angular Router. A refresh sends the URL directly to the web server, which may return 404 unless an SPA fallback is configured.
3. How do you handle Angular routing when ASP.NET Core hosts Angular?
Use static-file handling and an SPA fallback such as:
app.UseStaticFiles();
app.MapControllers();
app.MapFallbackToFile("index.html");
4. What does MapFallbackToFile("index.html") do?
It allows unmatched client-side application routes to return index.html, after which Angular Router processes the requested route.
5. What is the difference between Angular wildcard routing and server fallback?
Angular:
{ path: '**', component: NotFound }
handles unknown routes after Angular starts.
The server fallback gets the browser request to Angular in the first place:
Server → index.html → Angular → **
6. What does <base href="/"> mean?
It specifies the base URL from which Angular resolves routes and resources when the application is hosted at the domain root.
7. What if Angular is hosted under /portfolio/?
Configure the application for that base path, for example:
ng build --configuration production --base-href /portfolio/
and ensure the server's SPA fallback also handles routes beneath /portfolio/.
8. Why does Angular routing work with ng serve but not necessarily after deployment?
The Angular development server already handles SPA fallback behavior. The production web server must be configured separately.
9. What is hash routing?
Hash routing uses URLs such as:
/#/products
The route after # is handled client-side, reducing the need for server rewrite handling, although normal path-based routing is commonly preferred when server fallback is available.
10. Short interview answer
Angular uses client-side routing, so in production I configure the web server with an SPA fallback. If a request such as
/productsdoesn't match a physical file or backend endpoint, the server returnsindex.html, and Angular Router handles/products. With ASP.NET Core I can useMapFallbackToFile("index.html"); IIS, Nginx, Apache, and cloud static hosts use equivalent rewrite/fallback rules.