Ajax sites with real URL using history.pushState()

If your building a ajax based site then one of the problems you bound to come up on is how to handle pages. There common way to do this, the hastag (#), placing all the information you need to load the desired page after the tag and using the javascript window.location.hash to get it and process the request. This works great, I’ve used it myself before, but it does post 2 problems.

First, SEO. You can get hash based ajax sites to SEO, but it requires a lot of additional steps. There are several writups on the matter across the web but to summarize them google will take you hash tag and replace it with a get variable, so site.com/#load-this become site.com/?_escaped_fragment_=load-this. At this point all you have to do it make your script handle that appropriately, making sure to have the content that google seems be the same as what an ajax visitor sees (or google will get angry, and you want like google when it’s angry).

Second, no javascript no work. Theres no way the server can figure out the has tag and process it manually on it’s end, the browser wont be able to run anything checking if it has a hash tag or not. You can get fancy with the javascript but the best you can manage is having it redirect to a version of the site that does not use the has, always taking the person back to square one. Sometimes this is fine, sometimes it’s unacceptable.

Solving both problems at once

So if hash tags cause problems whats the solution? Why not just update the URL. Using the javascript has several history based functions that let you modify the users history to create pages that aren’t actually there, for this I suggest history.pushState(). This will let you modify the url as you want, giving you very normal looking URLs, but without reloading the page. Best of both worlds, unless your in IE. Unfortunately the current version of IE doesn’t support this, it’s going to gin support in version 10. You can just leave an (unfortunately) high number of visitors without access to the site, but theres a solution to that which I’ll talk about after we get the normal history.pushState() scripting out of the way. As with pretty much all my examples it’s done in jQuery, beacuse it makes everything easier.

history.pushState() example: server side

Note: None of this code has been tested, if it’s not working let me know and I’ll get something up that does work.

‘; } if($_POST[‘ajax_load’]!=’true’){ require_once(‘inc/header.php’); } require_once($_GET[‘path’].’.php’); if($_POST[‘ajax_load’]!=’true’){ echo ‘

‘; require_once(‘inc/footer.php’); } ?>

That code will load whatever the path you send it to and append .php to it so visitors don’t have to see your unsightly file extension. Notice how we check for a POST variable called ajax_load. This is the variable were going to send when you request the content of the page, that way we can leave the header and footer in place, and skip out of the load they require (which, depending on the complexity of your site could be pretty substantial). Obviously this is a pretty bare example, but it should give you the idea of what were trying to accomplish. To accompany this were going to need a little something in out .htaccess file. Now I’m traditional horrible with .htaccess files, so bear with the example.

That should take anything you throw at it and pass it to the page variable. In this example the page is just a direct path to the file, but you can easily get more advanced if you want. Personally I’ve used this in conjunction with wp_rewrite to create dynamic additional pages to for a wordpress plugin, allows me to make the search results “refresh” without having to reload the page creating a pretty slick effect.

history.pushState() example: client side

Now that we have the server side stuff taken care of we need to make the javascript that will power this all. This comes in two parts, first is the part that fires off the history.pushState(), second is the part that loads a new page and replaces the content. All these examples are written in jQuery, but theres nothing that can’t be done with vanilla javascript.

This bit of script may be tiny, but it’s key to getting proper fake URLs. This function could be called on any event, you could even bind it to fire on all a href‘s, thats all up to you. As you can see the function has 3 variables.

First is a change function, it will be called as the url is changed (either before or after, it’s so close to the timing it’s hard to tell and I don’t tend to use it, hence it being blank).

The second variable is the new page title, this is interesting since it doesn’t actually do anything in any browser. If you really must have it then you can change the title separately with document.title (you can even get real fancy and have the ajax return the title, and then change it based on that).

The third is the new URL. This can be either a full domain, or just a new request path. This will be added to the URL and a new item will be places in the visitors history just like they visit a real page. For this example this URL will be the same as the path to the file, the same path that our php will be reading at line 10. This is how the user will easily be able to go back tot he page they were on and, share a normal looking, SEO friendly link.

There second part of the client side code is simply an ajax call, jQuery makes it incredibly simple.

This is where it all comes together. The url will be handled by the .htacces file and pass it to you index.php file under the page get variable. Along with this it’ll send the ajax_load=true post with variable that tells out PHP what not to load (the things were not getting rid of). Then it replaces the output. The user seems the page “refresh” with new content without ever having it reload, at the same time being given a new URL that looks, shares, and most importantly SEOs normally. Best of both the ajax and normal site systems together.

The caveat

No solution is perfect, theres always a downside. In this case the biggest one is he lack of IE support. There are some solutions to this however. First, if you doing this as an entire link replacement system you can always just not replace links on IE. Internet Explorer has a very heavy cache file making loads between the same site usually quite quick. This can be done with with an IE conditional or a try{}catch(){}. I suggest you use that method as it will also include other older and unusual browsers. The second solution is to have to degrade to a hash tag based ajax system. While this will keep everything ajax your still going to need that non-ajax solution, both for users with javascript turned off (the small minority I know) and those on older and unusual browsers.

The other potential problem is that in the users history, all the page names will be the same. At some point I’m sure browsers will start supporting this (I would at least hope so) but for now it’s not something you can do about. This problem also persists in hash tag based ajax sites as well, so if your going ajax there really is no avoiding it.

Wait, isn’t this a security problem

I mean, you can change the url on your site to anything right? Wouldn’t it make spoofing sites easy? As it turns out no, while the url can be either the full domain or just a new path, it cannot be a different domain, that will just throw an error. I have this being shown in action on the history.pushState() example. It will kill your scripts without throwing anything to the console log in chrome however, so if your going all whilly-nilly replacing everything it might be a good idea to wrap it in a try tag just to be on the safe side.

That’s the basics

That all you should need to get started with this, I’ve been working on a project that uses this extensively to search an inventory, that I may post once it’s done as an example of how far you can go with this. In the mean time the code for it is up on Snipplr (coming soon hopefully, they seem to be having issues) and as a Github Gist. Like always if you have any questions leave them here, or at the relevant Google + Post, I’ll get back and try to help as soon as I can. Hope you find this useful, and ignore the italic text below.

Total side note in no way related to this, I made a small CSS change to make code elements stand out more, and I told you to ignore this.

Leave a Reply

Your email address will not be published. Required fields are marked *