[angular] Angular 2.0 router not working on reloading the browser

I am using Angular 2.0.0-alpha.30 version. When redirect to a different route, then refresh the browser , its showing Cannot GET /route.

Can you help me with figuring why this error happened.

This question is related to angular angular2-routing

The answer is


If you want to be able to enter urls in browser without configuring your AppServer to handle all requests to index.html, you must use HashLocationStrategy.

The easiest way to configure is using:

RouterModule.forRoot(routes, { useHash: true })

Instead of:

RouterModule.forRoot(routes)

With HashLocationStrategy your urls gonna be like:

http://server:port/#/path

Make sure this is placed in the head element of your index.html:

<base href="/">

The Example in the Angular2 Routing & Navigation documentation uses the following code in the head instead (they explain why in the live example note of the documentation):

<script>document.write('<base href="' + document.location + '" />');</script>

When you refresh a page this will dynamically set the base href to your current document.location. I could see this causing some confusion for people skimming the documentation and trying to replicate the plunker.


I wanted to preserve the URL path of sub pages in HTML5 mode without a redirect back to index and none of the solutions out there told me how to do this, so this is how I accomplished it:

Create simple virtual directories in IIS for all your routes and point them to the app root.

Wrap your system.webServer in your Web.config.xml with this location tag, otherwise you will get duplicate errors from it loading Web.config a second time with the virtual directory:

<configuration>
    <location path="." inheritInChildApplications="false">
    <system.webServer>
        <defaultDocument enabled="true">
            <files>
                <add value="index.html" />
            </files>
        </defaultDocument>
    </system.webServer>
  </location>
</configuration>

Server configuration is not a solution for an SPA is what even I think. You dont want to reload an angular SPA again if a wrong route comes in, do you? So I will not depend on a server route and redirect to other route but yes I will let index.html handle all the requests for angular routes of the angular app path.

Try this instead of otherwise or wrong routes. It works for me, not sure but seems like work in progress. Stumbled this myself when facing an issue.

@RouteConfig([
  { path: '/**', redirectTo: ['MycmpnameCmp'] }, 
   ...
  }
])

https://github.com/angular/angular/issues/4055

However remember to configure your server folders and access right in case you have HTML or web scripts which are not SPA. Else you will face issues. For me when facing issue like you it was a mix of server configuration and above.


This is not a permanent Fix for the problem but more like a workaround or hack

I had this very same issue when deploying my Angular App to gh-pages. First i was greeted with 404 messages when Refreshing my pages on gh-pages.

Then as what @gunter pointed out i started using HashLocationStrategy which was provided with Angular 2 .

But that came with it's own set of problems the # in the url it was really bad made the url look wierd like this https://rahulrsingh09.github.io/AngularConcepts/#/faq.

I started researching out about this problem and came across a blog. I tried giving it a shot and it worked .

Here is what i did as mentioned in that blog.

You’ll need to start by adding a 404.html file to your gh-pages repo that contains an empty HTML document inside it – but your document must total more than 512 bytes (explained below). Next put the following markup in your 404.html page’s head element:

<script>
  sessionStorage.redirect = location.href;
</script>
<meta http-equiv="refresh" content="0;URL='/REPO_NAME_HERE'"></meta>

This code sets the attempted entrance URL to a variable on the standard sessionStorage object and immediately redirects to your project’s index.html page using a meta refresh tag. If you’re doing a Github Organization site, don’t put a repo name in the content attribute replacer text, just do this: content="0;URL='/'"

In order to capture and restore the URL the user initially navigated to, you’ll need to add the following script tag to the head of your index.html page before any other JavaScript acts on the page’s current state:

<script>
  (function(){
    var redirect = sessionStorage.redirect;
    delete sessionStorage.redirect;
    if (redirect && redirect != location.href) {
      history.replaceState(null, null, redirect);
    }
  })();
</script>

This bit of JavaScript retrieves the URL we cached in sessionStorage over on the 404.html page and replaces the current history entry with it.

Reference backalleycoder Thanks to @Daniel for this workaround.

Now the above URL changes to https://rahulrsingh09.github.io/AngularConcepts/faq


I fixed this (using Java / Spring backend) by adding a handler that matches everything defined in my Angular routes, that sends back index.html instead of a 404. This then effectively (re)bootstraps the application and loads the correct page. I also have a 404 handler for anything that isn't caught by this.

@Controller         ////don't use RestController or it will just send back the string "index.html"
public class Redirect {

    private static final Logger logger = LoggerFactory.getLogger(Redirect.class);

    @RequestMapping(value = {"comma", "sep", "list", "of", "routes"})
    public String redirectToIndex(HttpServletRequest request) {
        logger.warn("Redirect api called for URL {}. Sending index.html back instead. This will happen on a page refresh or reload when the page is on an Angular route", request.getRequestURL());
        return "/index.html";
    }
}

2017-July-11: Since this is linked from a question having this problem but using Angular 2 with Electron, I'll add my solution here.

All I had to do was remove <base href="./"> from my index.html and Electron started reloading the page again successfully.


If you want to use PathLocationStrategy :

  • Wildfly configuration :
    • Create undertow-handlers.conf file to be placed in the WEB-INF
    • Content : (exclude your rest endpoints !)
      • regex['(./overview/?.?$)'] and not regex['(./endpoints.)'] -> rewrite['/index.html']
      • regex['(./deployments/?.?$)'] and not regex['(./endpoints.)'] -> rewrite['/index.html']

Single page application with Java EE/Wildfly: server-side configuration


i Just adding .htaccess in root.

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
    RewriteRule ^ - [L]
    RewriteRule ^ ./index.html
</IfModule>

here i just add dot '.'(parent directory) in /index.html to ./index.html
make sure your index.html file in base path is main directory path and set in build of project.


For those of us struggling through life in IIS: use the following PowerShell code to fix this issue based on the official Angular 2 docs (that someone posted in this thread? http://blog.angular-university.io/angular2-router/)

Import-WebAdministration
# Grab the 404 handler and update it to redirect to index.html.
$redirect = Get-WebConfiguration -filter "/system.WebServer/httperrors/error[@statusCode='404']" -PSPath IIS:\Sites\LIS 
$redirect.path = "/index.html"
$redirect.responseMode = 1
# shove the updated config back into IIS
Set-WebConfiguration -filter "/system.WebServer/httperrors/error[@statusCode='404']" -PSPath IIS:\Sites\LIS -value $redirect

This redirects the 404 to the /index.html file as per the suggestion in the Angular 2 docs (link above).


for angular 5 quick fix, edit app.module.ts and add {useHash:true} after the appRoutes.

@NgModule(
{
  imports:[RouterModule.forRoot(appRoutes,{useHash:true})]
})

Angular by default uses HTML5 pushstate (PathLocationStrategy in Angular slang).
You either need a server that processes all requests like it were requesting index.html or you switch to HashLocationStrategy (with # in the URL for routes) https://angular.io/docs/ts/latest/api/common/index/HashLocationStrategy-class.html

See also https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/

To switch to HashLocationStrategy use

update for >= RC.5 and 2.0.0 final

import {HashLocationStrategy, LocationStrategy} from '@angular/common';

@NgModule({
  declarations: [AppCmp], 
  bootstrap: [AppCmp],
  imports: [BrowserModule, routes],
  providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
]);

or shorter with useHash

imports: [RouterModule.forRoot(ROUTER_CONFIG, {useHash: true}), ...

ensure you have all required imports

For the new (RC.3) router

<base href="."> 

can cause 404 as well.

It requires instead

<base href="/">

update for >= RC.x

bootstrap(AppCmp, [
  ROUTER_PROVIDERS,
  provide(LocationStrategy, {useClass: HashLocationStrategy})
  // or since RC.2
  {provide: LocationStrategy, useClass: HashLocationStrategy} 
]);

import {provide} from '@angular/core';
import {  
  PlatformLocation,  
  Location,  
  LocationStrategy,  
  HashLocationStrategy,  
  PathLocationStrategy,  
  APP_BASE_HREF}  
from '@angular/common';  

update for >= beta.16 Imports have changed

import {BrowserPlatformLocation} from '@angular/platform-browser';

import {provide} from 'angular2/core';
import {
  // PlatformLocation,
  // Location,
  LocationStrategy,
  HashLocationStrategy,
  // PathLocationStrategy,
  APP_BASE_HREF}
from 'angular2/router';
import {BrowserPlatformLocation} from 'angular2/src/router/location/browser_platform_location';

< beta.16

import {provide} from 'angular2/core';
import {
  HashLocationStrategy
  LocationStrategy,
  ROUTER_PROVIDERS,
} from 'angular2/router';

See also https://github.com/angular/angular/blob/master/CHANGELOG.md#200-beta16-2016-04-26 breaking-changes


If you are using Apache or Nginx as a server you have to create a .htaccess (if not created before) and "On" RewriteEngine

RewriteEngine On  
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
RewriteRule ^ /index.html

This is not the right answer but On-refresh you can redirect all dead calls to Homepage by sacrificing 404 page it's a temporary hack just replay following on 404.html file

<!doctype html>
<html>
    <head>
        <script type="text/javascript">
            window.location.href = "http://" + document.location.host;
        </script>
    </head>
</html>

You can try out below. It works for me!

main.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

...
export class MainComponent implements OnInit {
    constructor(private router: Router) {
        let path: string = window.location.hash;
        if (path && path.length > 0) {
            this.router.navigate([path.substr(2)]);
        }
    }

    public ngOnInit() { }
}

You can further enhance path.substr(2) to split into router parameters. I'm using angular 2.4.9


I think you are getting 404 because your are requesting http://localhost/route which doesn't exist on tomcat server. As Angular 2 uses html 5 routing by default rather than using hashes at the end of the URL, refreshing the page looks like a request for a different resource.

When using angular routing on tomcat you need to make sure that your server will map all routes in your app to your main index.html while refreshing the page. There are multiple way to resolve this issue. Whichever one suits you you can go for that.

1) Put below code in web.xml of your deployment folder :

<error-page>
     <error-code>404</error-code>
     <location>/index.html</location>
</error-page>

2) You can also try using HashLocationStrategy with # in the URL for routes :

Try using:

RouterModule.forRoot(routes, { useHash: true })

Instead of:

RouterModule.forRoot(routes)

With HashLocationStrategy your urls gonna be like:

http://localhost/#/route

3) Tomcat URL Rewrite Valve : Re-write the url's using a server level configuration to redirect to index.html if the resource is not found.

3.1) Inside META-INF folder create a file context.xml and copy the below context inside it.

<? xml version='1.0' encoding='utf-8'?>
<Context>
  <Valve className="org.apache.catalina.valves.rewrite.RewriteValve" />
</Context>

3.2) Inside WEB-INF, create file rewrite.config(this file contain the rule for URL Rewriting and used by tomcat for URL rewriting). Inside rewrite.config, copy the below content:

  RewriteCond %{SERVLET_PATH} !-f
  RewriteRule ^/(.*)$ /index.html [L]

I had the same problem with using webpack-dev-server. I had to add the devServer option to my webpack.

Solution:

// in webpack
devServer: {
    historyApiFallback: true,
    stats: 'minimal'
}

Simon's answer was correct for me. I added this code:

app.get('*', function(req, res, next) {
  res.sendfile("dist/index.html");
});

The best solution of resolve the "router-not-working-on-reloading-the-browser" is that we should use spa-fall back. If you are using angular2 application with asp.net core then we need to defined it on "StartUp.cs" page. under MVC routs. I am attaching the code.

 app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
            routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" });
        });

The answer is quite tricky. If you use a plain old Apache Server (or IIS), you get the problem because the Angular pages do not exist for real. They are "computed" from the Angular route.

There are several ways to fix the issue. One is to use the HashLocationStrategy offered by Angular. But a sharp sign is added in the URL. This is mainly for compatibility with Angular 1 (I assume). The fact is the part after the sharp is not part of the URL (then the server resolves the part before the "#" sign). That can be perfect.

Here an enhanced method (based on the 404 trick). I assume you have a "distributed" version of your angular application (ng build --prod if you use Angular-CLI) and you access the pages directly with your server and PHP is enabled.

If your website is based on pages (Wordpress for example) and you have only one folder dedicated to Angular (named "dist" in my example), you can do something weird but, at the end, simple. I assume you have stored your Angular pages in "/dist" (with the according <BASE HREF="/dist/">). Now use a 404 redirection and the help of PHP.

In your Apache configuration (or in the .htaccess file of your angular application directory), you must add ErrorDocument 404 /404.php

The 404.php will start with the following code:

<?php
$angular='/dist/';
if( substr($_SERVER['REQUEST_URI'], 0, strlen($angular)) == $angular ){
    $index = $_SERVER['DOCUMENT_ROOT'] . $angular . "index.html";
    http_response_code(200);
    include $index;
    die;
}

// NOT ANGULAR...
echo "<h1>Not found.</h1>"

where $angular is the value stored in the HREF of your angular index.html.

The principle is quite simple, if Apache does not find the page, a 404 redirection is made to the PHP script. We just check if the page is inside the angular application directory. If it is the case, we just load the index.html directly (without redirecting): this is necessary to keep the URL unchanged. We also change the HTTP code from 404 to 200 (just better for crawlers).

What if the page does not exist in the angular application? Well, we use the "catch all" of the angular router (see Angular router documentation).

This method works with an embedded Angular application inside a basic website (I think it will be the case in future).

NOTES:

  • Trying to do the same with the mod_redirect (by rewriting the URLs) is not at all a good solution because files (like assets) have to be really loaded then it is much more risky than just using the "not found" solution.
  • Just redirecting using ErrorDocument 404 /dist/index.html works but Apache is still responding with a 404 error code (which is bad for crawlers).

This is a common situation in all router versions if you are using the default HTML location strategy.

What happens is that the URL on the browser bar is a normal full HTML url, like for example: http://localhost/route.

So when we hit Enter in the browser bar, there is an actual HTTP request sent to the server to get a file named route.

The server does not have such file, and neither something like express is configured on the server to handle the request and provide a response, so the server return 404 Not Found, because it could not find the route file.

What we would like is for the server to return the index.html file containing the single page application. Then the router should kick in and process the /route url and display the component mapped to it.

So to fix the issue we need to configure the server to return index.html (assuming that is the name of your single page application file) in case the request could not be handled, as opposed to a 404 Not Found.

The way to do this will depend on the server side technology being used. If its Java for example you might have to write a servlet, in Rails it will be different, etc.

To give a concrete example, if for example you are using NodeJs, you would have to write a middleware like this:

function sendSpaFileIfUnmatched(req,res) {
    res.sendFile("index.html", { root: '.' });
}

And then register it at the very end of the middleware chain:

app.use(sendSpaFileIfUnmatched);

This will serve index.html instead of returning a 404, the router will kick in and everything will work as expected.


Disclaimer: this fix works with Alpha44

I had the same issue and solved it by implementing the HashLocationStrategy listed in the Angular.io API Preview.

https://angular.io/docs/ts/latest/api/common/index/HashLocationStrategy-class.html

Start by importing the necessary directives

import {provide} from 'angular2/angular2';
import {
  ROUTER_PROVIDERS,
  LocationStrategy,
  HashLocationStrategy
} from 'angular2/router';

And finally, bootstrap it all together like so

bootstrap(AppCmp, [
  ROUTER_PROVIDERS,
  provide(LocationStrategy, {useClass: HashLocationStrategy})
]);

Your route will appear as http://localhost/#/route and when you refresh, it will reload at it's proper place.

Hope that helps!


I think the error you are seeing is because your are requesting http://localhost/route which doesn't exist. You need to make sure that your server will map all requests to your main index.html page.

As Angular 2 uses html5 routing by default rather than using hashes at the end of the url, refreshing the page looks like a request for a different resource.


If you are using Apache or Nginx as a server you need to create a .htaccess

<IfModule mime_module>
      AddHandler application/x-httpd-ea-php72 .php .php7 .phtml
    </IfModule>
    # php -- END cPanel-generated handler, do not edit

    <IfModule mod_rewrite.c>
        RewriteEngine on

        # Don't rewrite files or directories
        RewriteCond %{REQUEST_FILENAME} -f [OR]
        RewriteCond %{REQUEST_FILENAME} -d
        RewriteRule ^ - [L]

        # Rewrite everything else to index.html
        # to allow html5 state links
        RewriteRule ^ index.html [L]
    </IfModule>

You can use location strategy, Add useHash: true in routing file.

imports: [RouterModule.forRoot(routes, { useHash: true })]


Angular apps are perfect candidates for serving with a simple static HTML server. You don't need a server-side engine to dynamically compose application pages because Angular does that on the client-side.

If the app uses the Angular router, you must configure the server to return the application's host page (index.html) when asked for a file that it does not have.

A routed application should support "deep links". A deep link is a URL that specifies a path to a component inside the app. For example, http://www.example.com/heroes/42 is a deep link to the hero detail page that displays the hero with id: 42.

There is no issue when the user navigates to that URL from within a running client. The Angular router interprets the URL and routes to that page and hero.

But clicking a link in an email, entering it in the browser address bar, or merely refreshing the browser while on the hero detail page — all of these actions are handled by the browser itself, outside the running application. The browser makes a direct request to the server for that URL, bypassing the router.

A static server routinely returns index.html when it receives a request for http://www.example.com/. But it rejects http://www.example.com/heroes/42 and returns a 404 - Not Found error unless it is configured to return index.html instead

If this issue occurred in production, follow below steps

1) Add a Web.Config file in the src folder of your angular application. Place below code in it.

<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>

2) Add a reference to it in angular-cli.json. In angular-cli.json put Web.config in assets block as shown below.

"assets": [
    "assets",
    "favicon.ico",
    "Web.config"
  ],

3) Now you can build the solution for production using

ng build --prod

This will create a dist folder. The files inside dist folder are ready for deployment by any mode.


Add imports:

import { HashLocationStrategy, LocationStrategy } from '@angular/common';

And in NgModule provider, add:

providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]

In main index.html File of the App change the base href to ./index.html from /

The App when deployed in any server will give a real url for the page which can be accessed from any external application.


My server is Apache, what I did to fix 404 when refreshing or deep linking is very simple. Just add one line in apache vhost config:

ErrorDocument 404 /index.html

So that any 404 error will be redirected to index.html, which is what angular2 routing wants.

The whole vhost file example:

<VirtualHost *:80>
  ServerName fenz.niwa.local
  DirectoryIndex index.html
  ErrorDocument 404 /index.html

  DocumentRoot "/Users/zhoum/Documents/workspace/fire/fire_service/dist"
  ErrorLog /Users/zhoum/Documents/workspace/fire/fire_service/logs/fenz.error.log
  CustomLog /Users/zhoum/Documents/workspace/fire/fire_service/logs/fenz.access.log combined

  <Directory "/Users/zhoum/Documents/workspace/fire/fire_service/dist">
    AllowOverride All
    Options Indexes FollowSymLinks
    #Order allow,deny
    #Allow from All
    Require all granted
  </Directory>

  Header set Access-Control-Allow-Origin "*"
  Header set Access-Control-Allow-Methods "GET, POST"
  Header set Access-Control-Allow-Credentials "true"
  Header set Access-Control-Allow-Headers "Accept-Encoding"
</VirtualHost>

No matter what server you are using, I think the whole point is finding out the ways to config the server to redirect 404 to your index.html.


Just adding .htaccess in root resolved 404 while refreshing the page in angular 4 apache2.

<IfModule mod_rewrite.c>
    RewriteEngine on

    # Don't rewrite files or directories
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]

    # Rewrite everything else to index.html
    # to allow html5 state links
    RewriteRule ^ index.html [L]
</IfModule>

I checked in angular 2 seed how it works.

You can use express-history-api-fallback to redirect automatically when a page is reload.

I think it's the most elegant way to resolve this problem IMO.


You can use this solution for mean application, I used ejs as view engine:

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.engine('html', require('ejs').renderFile);
app.use(function (req, res, next) {
    return res.render('index.html');
});

and also set in angular-cli.json

"apps": [
    {
      "root": "src",
      "outDir": "views",

it will work fine instead of

app.get('*', function (req, res, next) {
    res.sendFile('dist/index.html', { root: __dirname });
 });

its creating issue with get db calls and returning index.html