Search code examples
javascriptjqueryajaxhtml5-history

history.pushState URL interaction with jquery load causing page fetch failure?


I'm using history.pushState to set a URL, while at the same time using a jquery .load to actually load the page content. I'm new to the history API, but I understand that this is the normal way to get back-button/etc functionality in dynamic sites.

The problem is that the spoofed address I'm setting with pushState is being used by .load in some way when it fetches pages, which means that .load is trying to fetch non-existant pages, and so fails. DocumentRoot is at /var/www/bar on the server. In this example, the user is attempting to navigate between two points in a single html file on the server, which is located at dir1/dir2/p1.html under DocumentRoot on the server.

  1. When the user clicks on link 1, the server should return dir1/dir2/p1.html #Container1, and the address bar should show foo.com/a/b/c
  2. When the user clicks on link 2, the server should return dir1/dir2/p1.html #Container2, and the address bar should show foo.com/a/b/d

So how exactly do I do this? This is what I've tried with relative URLs, and it fails:

$("#mainContent").load("dir1/dir2/p1.html #container1");  // good: XHR fetch foo.com/bar/dir1/dir2/p1.html
history.pushState(null, null, "a/b/c");                   // good: shows 'foo.com/a/b/c' as the URL
...
$("#mainContent").load("dir1/dir2/p1.html #container2");  // bad: XHR fetch foo.com/bar/a/b/dir1/dir2/p1.html
history.pushState(null, null, "a/b/d");                   // bad: shows 'foo.com/a/b/a/b/d' as the URL

And this is what happens when I use absolute URLs:

$("#mainContent").load("/dir1/dir2/p1.html #container1");  // good: XHR fetch foo.com/bar/dir1/dir2/p1.html
history.pushState(null, null, "a/b/c");                    // good: shows 'foo.com/a/b/c' as the URL
...
$("#mainContent").load("/dir1/dir2/p1.html #container2");  // bad: XHR fetch foo.com/a/b/dir1/dir2/p1.html
history.pushState(null, null, "a/b/d");                    // good: shows 'foo.com/a/b/d' as the URL

Note that the absolute paths are even worse, in that the XHR fetches don't even use DocumentRoot (bar) any more. Thanks.

EDIT 1

relative case tested on Chrome/Windows and Firefox/Linux with the same results; absolute case tested only on Firefox/Linux.

EDIT 2

I've been experimenting and found two ways to fix this, but I don't really understand what's going on, so I won't post this as an answer:

  1. Keep all URLs set by history to one level deep, so a/b/c becomes a-b-c. All the stuff I've googled on the history API shows only one-level-deep URLs, so maybe this is common
  2. Add a base tag to the site's <head> (<base "href=http://example.com/" target="_blank">

Solution

  • Changing the history state will change the document's URL and thus its baseURI if there is no other things that did already change it, like a <base> element.

    So all future network requests will indeed be made from this new base URI.

    To workaround that, you can manually set back the baseURI after you changed you history state,

    console.log( document.baseURI ); // /js
    
    history.pushState(null, null, '/foo');
    console.log( document.baseURI ); // /foo
    
    // manually set document's baseURI via <base>
    const base = document.createElement('base');
    base.href = 'bar';
    document.head.append(base);
    console.log( document.baseURI ); // /bar

    As a jsfiddle for users facing StackSnippet's null origined iframes restrictions

    Or you could even just append this <base> element only once in your document from the first page load.