Angular's router shows a URL like /#/products/5 by default in some setups — switching to a clean URL like /products/5 is a one-line application change, but it requires web server configuration to actually work correctly once deployed.
Enabling clean (PathLocationStrategy) URLs
// app.config.ts (standalone) or app-routing.module.ts (NgModule)
import { provideRouter, withHashLocation } from '@angular/router';
// Default (no hash) — this is actually already PathLocationStrategy by default
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes)],
};
Modern Angular defaults to PathLocationStrategy (clean URLs, no hash) already — the hash-based HashLocationStrategy is the one that needs to be explicitly opted into via withHashLocation(), not the other way around, which is a common point of confusion for anyone following an older tutorial.
Explicitly enabling hash-based routing, for comparison
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes, withHashLocation())],
};
Why clean URLs need web server configuration to actually work
With a hash-based URL, everything after the # is never sent to the server at all — the server only ever sees a request for /, and Angular's router handles the rest client-side. With a clean URL like /products/5, a hard refresh (or someone pasting the link directly) sends an actual request for /products/5 to the server — which has no real file or route at that path, since it's Angular's client-side router that's supposed to handle it, not the server.
The fix: rewriting every path back to index.html
# nginx
location / {
try_files $uri $uri/ /index.html;
}
# Apache .htaccess
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
This is the essential missing piece — the server is configured to serve index.html (the Angular app's entry point) for any path that doesn't match a real file, letting Angular's own router take over and correctly render the requested route client-side once the app loads.
Setting the base href correctly
The tag tells Angular's router what the application's root path is — this needs to correctly match the actual deployment path (usually just / for a site served from the domain root, but a subdirectory path if deployed elsewhere), or relative routing and asset loading can break in confusing ways.
Why hash-based routing is still sometimes chosen deliberately
Hash-based routing remains genuinely useful when deploying to a static host with no ability to configure server-side rewrite rules at all (some very basic static file hosts, or an app embedded as a widget on an existing non-Angular page) — since everything after the # never reaches the server, hash routing works correctly with zero server configuration, at the cost of the less clean-looking URL.