Tell me more ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I'm using a nodeJS server with Express to serve my AngularJS application. I'm also using requireJS to load all my scripts. This all works fine when I'm using angularJS default routes (hashbangs), but now I'm trying to activate html5 mode.

I'm activating html5mode like this:

$locationProvider.html5Mode(true).hashPrefix('!');

And this is what my nodeJS app.js file looks like:

var path     = require('path'),
    express  = require('express'),
    app      = express(),
    routes   = require(path.join(__dirname, 'routes'));

app.configure(function() {
    app.use(express.logger('dev'));
    app.use(express.compress());
    app.use(express.methodOverride());
    app.use(express.bodyParser());
    app.use(app.router);
    app.all("/*", function(req, res, next) {
        res.sendfile("index.html", { root: __dirname + "/../app" });
    });
    app.use(express.errorHandler({
        dumpExceptions: true, 
        showStack: true
    }));
});

However, this now serves all requests as HTML files (I think).. And so I get the following error from requireJS:

Uncaught SyntaxError: Unexpected token < 

I tried adding the following to my nodeJS app.js so it would serve my resources correctly:

app.use("/js", express.static(__dirname + "/../app/js"));
app.use("/img", express.static(__dirname + "/../app/img"));
app.use("/css", express.static(__dirname + "/../app/css"));
app.use("/partials", express.static(__dirname + "/../app/partials"));

but still no luck.

I also tried replacing the app.all statement with:

app.use(function(req, res) {
  // Use res.sendfile, as it streams instead of reading the file into memory.
  res.sendfile(__dirname + '/../app/index.html');
});

but that didn't work either. What can I do to get angularJS html5mode working with nodeJS and requireJS? Thanks.

share|improve this question
add comment

2 Answers

up vote 1 down vote accepted

Your initial fix (declaring static middleware handlers for specific prefixes) should work just fine, but you need to make sure they are declared before any other routes (and app.router, although you don't need to explicitly use it):

// these need to go first:
app.use("/js", express.static(__dirname + "/../app/js"));
app.use("/img", express.static(__dirname + "/../app/img"));
app.use("/css", express.static(__dirname + "/../app/css"));
app.use("/partials", express.static(__dirname + "/../app/partials"));

// any other routes:
app.all("/*", ...);

Also, you need to make sure that the prefixed static handlers are actually declared okay (correct path), otherwise they won't be able to find any requested files and the requests will pass down the middleware chain and ultimately be handled by the catch-all handler (should be easy enough to test by commenting out the catch-all handler and see if any JS/CSS/... requests work okay).

share|improve this answer
 
exactly, and if you're using a proxy server like NGINX then you might as well set the dependency routing at the proxy level –  Bent Cardan Oct 7 at 22:28
add comment

I like @robertklep's answer because his reasoning is solid, but I disagree with the unix style redirect *

I also disagree with hashbangs and can't understand why you want to use a hashbang. I think we stopped trying to develop links with those a while back. Read this article on why http://www.tbray.org/ongoing/When/201x/2011/02/09/Hash-Blecch

Express is great for SPAs and you can enable intelligent routing by an anonymous function that inserts a hash in the URL at the end of the middleware. You can modify it to suit your needs, but I usually throw it after the routing middleware and any calls to the static method. I go like this in my express apps:

app.use(express.bodyParser())
app.use(app.router)
app.use(require('stylus').middleware({src: __dirname + '/public',   compress: true}))
app.use(express.static(path.join(__dirname, '/public')))
app.use(function(req, res) {
  return res.redirect(req.protocol + '://' + req.get('Host') + '/#' + req.url)
})

For me this is a best practice in writing SPAs. I use the same function with Backbone/Express, especially when I upgrade the Backbone router to remove the ugly hash following the HTML5 location API. Check out my fork of the express angular seed and stop with that overbearing style redirect in our server logic. Just let the client application decide where to redirect people. It's important to let the client decide this because it preserves application state later when their initial request URL differs from the first client index we configured. Why not let people bookmark URLs across your app and arrive there in one request? You may see a brief initial load but immediately thereafter, our clientside JavaScript executes the needful as we slip that hash in before their req.params and BOOM! Now they can't land on anything but a single index and any meaningful parameters available to the clientside router logic.

share|improve this answer
 
Am I correct that this wouldn't work with HTML5 history api style URLs? If you do use html5 URLs, Angular provides a polyfill for that, so you would need to let Angular decide when to use a hash. –  Kevin C. Oct 29 at 20:24
 
no, I use this and pass $locationProvider.html5Mode(true) to the config object/function when setting up my routes. –  Bent Cardan Oct 29 at 20:49
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.