Using HTML5, Not Hash Routes

By default, AngularJS’ $locationProvider defaults to URLs like:


If in the future you may decide on one of the following:

Then, you should switch to HTML5 routing instead of using the default hash URLs.

Besides, it’s easy and takes 2 minutes.

Step 1: Re-enable X-Requested-With Header

For some reason, this was removed around v1.1.5, but it’s crucial for your AngularJS app to tell your server when it’s making AJAX requests for, say, a ng-include or templateUrl.

// client/app.js
angular.module('app', []).config([
  function ($httpProvider) {
    // Expose XHR requests to server
    $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'

Step 2: Enable HTML5 Routing

// client/app.js
angular.module('app', []).config([
  function ($httpProvider, $locationProvider) {
    // Expose XHR requests to server
    $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'

    // This is `false` by default

Step 3: Create a Wildcard Route

Assuming you’re using express, the very last route you define will be you’re catch-all. This way, any routes you explicitly define on the server-side are handled there, whiles any other routes render the client-side page, AngularJS to handle.

// server/app.js
app.get('/*', function (req, res) {
  // AJAX requests are aren't expected to be redirected to the AngularJS app
  if (req.xhr) {
    return res.status(404).send(req.url + ' not found')

  // `sendfile` requires the safe, resolved path to your AngularJS app
  res.sendfile(path.resolve(__dirname + '/path/to/index.html'))

The important part is the check for req.xhr. If you didn’t include this, then using ng-include with a missing template would recursively keep including your broken application.


Even though this solution has worked quite well in my experience (plus, it just feels right), there is one draw back: 404s are now 200s, as they’re now rendered by the client.

Luckily, this can be remedied a number of ways.

For example, if your server-side application consists of primarily a /api root URL, then you can have /api/* act as a catchall to send a legitimate 404.

If you want to explicitly serve your AngularJS app only for AngularJS routes, then you’ll have to do more legwork, where your routes are defined as a Object for use by both the client and server for defining routes.


Even with those minor nitpicks, I prefer my SPAs to look & act like proper, server-rendered sites as much as possible, especially with minimal effort.