AngularJS + Martini: html5mode
By default AngularJS displays URL paths prefixed with a # symbol. This enables backwards compatibility with browsers that don’t support HTML5 history API. The AngularJS guide explains this in detail here
To remove the # symbol and display more normal-looking URLs requires the use of html5mode
in AngularJS. This is enabled via $locationProvider, for example:
app.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
$routeProvider.
when('/signin', {
templateUrl: 'components/signin.html',
controller: 'SigninCtrl'
}).
when('/', {
templateUrl: 'components/home.html',
controller: 'HomeCtrl'
}).
otherwise({
redirectTo: '/'
});
}
]);
If the client starts browsing at /
then this will work fine. The webserver sends index.html
to the client and AngularJS can load itself up ready to handle the other routes itself. A problem occurs if the client starts browsing directly to a path that the webserver may not know about, for example /signin
. When the web server receives that request it will return a HTTP 404
error. What we need to do instead is tell our server to serve the contents of index.html
. Note, we are not performing a HTTP redirect here.
I’m using Go to serve my static content, and using the Martini web framework to make life a bit easier.
To have Martini to do the necessary rewrite, perform the following setup:
router := martini.NewRouter()
router.NotFound(func(w http.ResponseWriter, r *http.Request) {
// Only rewrite paths *not* containing filenames
if path.Ext(r.URL.Path) == "" {
http.ServeFile(w, r, "public/index.html")
} else {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("404 page not found"))
}
})
m := martini.New()
m.Use(martini.Logger())
m.Use(martini.Recovery())
m.Use(martini.Static("public"))
m.Action(router.Handle)
Change public
to suit whichever folder your web content lives in.