This guide explains the issues with using JavaScript’s HTML5 History (pushState) URLs in Apache and covers how to set up your .htaccess file to handle URL routing.
So we’ve reached this point now where every half-decent JavaScripter knows their way around the pushState / replaceState History API and the related wrappers/plugins, and has a decent idea of browser support and cross-browser polyfills for HTML5 History. I think.
But at the same time, the very people who want to be using it are faced with a blight that, for love nor money, they can’t fix: HTML5 History URLs screw up Apache. And there are just no damn guides anywhere about how to make it work (until now!)
The Apache web server was designed long before the first murmurings of rewriting URLs without a page refresh were heard, and a certain amount of tomfoolery is required to make dynamically-updating URLs work seamlessly.
So without further ado, I present the problem and the starter-pack solution.
Apache doesn’t like you with your damn pushState
So, as you hopefully know by now, to navigate between different sections, screens or pages of a client-side JavaScript application, you’d traditionally use hash (#) or hash-bang (#!) fragments (e.g. .com/#shopping-cart or .com/#!/profile/yourmum/). The JS code picks up the hashchange event (either natively or via a custom listener/event in older browsers) and fires off a function in your app to switch out the content, or perform some killer action.
When you use HTML5 History, you don’t use hash fragments; instead, your JS app or website uses .pushState(), or you use your library/wrapper of choice, to navigate (update the URL and create an entry in the window history, enabling Back/Forward support) to e.g. .com/shopping-cart or .com/profile/yourmum/ – all without a page refresh.
Perfect. But… when you save/bookmark/send these URLs, then try to visit them a little later on, you may be disappointed to discover that they don’t exist. The horror!
Why don’t they exist?
Think about it – you’re trying to find a resource located at a specific URL. It has to be served through Apache. Apache is gonna look for that file, because it doesn’t have any idea that you’re looking for a dynamic resource, and even if it did, it wouldn’t know what to send back to help you find it!
Apache can handle HTML History/pushState with .htaccess
Apache was just being mean, earlier. It doesn’t have anything against pushState. In fact, it loves pushState: now every user, instead of requesting 20 pages in quick succession, requests only one page, with those 20 states being requested bit-by-bit (as smaller fragments, or however your site or JS app handles it.)
But it does choke a little bit.
As I mentioned before, it has to follow the rules: when a visitor requests a resource (or a location that looks like a resource) Apache has to look for it, first in the .htaccess file, if any, then in the filesystem to see if it can find a matching directory/file.
It won’t find it, so it throws a 404 error. “I wanted to help,” it mumbles, “I just couldn’t find the damn thing you wanted. Not my problem!”
So we gotta tell it where to look!
Where would it look? Well, where is the JavaScript app that’s going to check the URL and route the page/screen/section accordingly?
For most apps, it’s index.html, which would include in all the JavaScript required to handle this. For example, that’s the page containing your single-page app, in Backbone.js, or jQuery, or just (whoa) plain ol’ JavaScript.
And that’s what this solution assumes.
.htaccess rules for HTML5 pushState support:
So the solution: you add a few lines to your .htaccess file.
These lines check firstly whether the requested filename is NOT a static resource (such as an image, stylesheet, or other asset, so that they don’t all get redirected to index.html), then whether the requested resource is not already index.html (uh oh infinite loops!) and then finally redirects everything to – you guessed it – index.html.
# html5 pushstate (history) support:
<ifModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !index
RewriteRule (.*) index.html [L]
</ifModule>
There might even be a nicer way to write it. I’m no Apache warrior, so to speak, but this works for us.
What does this mean for the visitor?
Obviously, when on the site/single-page app, they don’t notice a thing. Everything works fine.
The kicker is that when they bookmark a page, or email a funny link to their friend, the next time that URL is opened (the one that was navigated to by pushState), Apache will simply direct the visitor to the homepage. At that point, the JavaScript app takes over. Boom.
This works for multiple-levels of nesting, too, which was a big pain-point when finding a solution. Thus the following URL redirects to index.html without a fuss:
.com/profiles/yourmum/images/?starttime=[stalkerishly long time ago] -> .com/index.html
At which point the JavaScript code would step in and do it’s nasty business.
Et voila. The .htaccess rules above may not work for everyone and YMMV, but it’s doing it for us.
Further reading:
- Inspired by: mod_rewriting an entire site – workingwith.me.uk. Inspiration also taken from WordPress’
.htaccessrules. - Nice post: location.hash is dead. Long live HTML5 pushState! – badassJS
Any comments? Suggestions, improvements, errors? You know what to do!
PS. I know, I know. “Don’t use Apache blah blah nginx blah.” I know. But people still use Apache.
# edit: Updated .htaccess rules to be a bit better, based on WordPress permalinks’ .htaccess rules.

