Reader Stacks

How to Remove the Hash From Angular Route URLs

Switching to a clean, hash-free URL shifts routing responsibility onto the actual web server — without a rewrite rule pointing every path back to index.html, a hard refresh on any inner route 404s.

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.