How do I throw a real 404 or 301 with an Angular pushstate URL

Problem

I'm using $routeProvider and $locationProvider to handle pushstate URLS in a single page app (SPA), something like this:

angular.module('pets', [])

  .config(function($routeProvider, $locationProvider) {
    $locationProvider.html5Mode(true);
    $routeProvider.when('/pet/:petId', {
      controller: 'petController'
    });
  })

  .controller('petController', function($scope, petService, $routeParams){
    petService.get('/api/pets/' + $routeParams.petId).success(function(data) {
      $scope.pet = data;
    });
  });

The URL is used to pull content from the server which may or may not exist.

If this was an ordinary multipage website, a request for missing content would trigger a 404 header response from the server, and a request for moved content would trigger a 301. This would alert Google to the missing or moved content.

Say for example I hit a URL like this:

http://example.com/pet/123456

and say there is no such pet in the database, how can my SPA return a 404 on that content.

Failing this, is there some other way to correctly alert the user or search engine that the requested URL doesn't exist? Is there some other solution I'm not considering?

Problem courtesy of: superluminary

Solution

The real question is does http://example.com/pet/123456 return anything at all?

If your starting point is http://example.com/ and there's a link to http://example.com/pet/123456 then Angular will call the petController which in turn makes an AJAX call to http://example.com/api/pet/123456.

A crawler wouldn't do that but instead would try to call http://example.com/pet/123456 directly.

So your server must be able to handle that call. If there is no pet with the id 123456 then it should return 404. Problem solved. If there is then it should return the SPA. The application should then handle the situation accordingly.

Solution courtesy of: zeroflagL

Discussion

Try this

angular.module('pets', [])

  .config(function($routeProvider, $locationProvider) {
    $locationProvider.html5Mode(true);
    $routeProvider.when('/pet/:petId', {
      controller: 'petController'
    }). otherwise({ yourUrl:'/404.html'}) // Render 404 view;
  })

Discussion courtesy of: Anamika

According to this answer How do search engines deal with AngularJS applications?, You should use Headless Browser to process crawlers requests, and serve back snapshots of the page with the appropriate Response Code. https://developers.google.com/webmasters/ajax-crawling/docs/html-snapshot

The google example did not include 301,302 or 404 cases. However, their code could be modified to analyze the content of the snapshot and change the response code.

I found prerender.io offers this service, but it is not free. However, they have a free plan if you have fewer than 250 pages. Prerender asks that in case of 404 or 301, you add a meta tag to the DOM.

<meta name="prerender-status-code" content="404">

this meta tag is then detected by their headless browser and the response code is changed.

Discussion courtesy of: Roman Mik

This recipe can be found in it's original form on Stack Over Flow.