The only constant is change, continuing change, inevitable change, that is the dominant factor in society [and web apps!] today. No sensible decision can be made any longer without taking into account not only the world as it is, but the world as it will be.
– Isaac Asimov
R.I.P #!
It did not take us long to discover we shared the concerns of Twitter’s Dan Webb on hashbang routes, but it was almost a year before we were able to remove them from Artsy. Here’s how it went down.
Artsy relies on the Backbone.js framework for our client application which offers a solid pushState routing scheme. This includes a seamless hashtag fallback for browsers that don’t support the HTML5 History API (looking at you IE 9).
The pushState routing is optional, but “the world as it should be” suggests we say “Yes!” (or true) to pushState.
1
| |
The Client
At Artsy, we had left Backbone out of the loop for most of our internal linking. Our markup href attributes all began with ‘/#!’ and expected the browser’s default hash behavior to keep the page from refreshing. For a proper pushState scheme, the app’s internal linking should begin with an absolute route. Backbone.js defaults to ‘/’, but this is configurable.
1 2 3 4 | |
Internal Links
All internal links need to begin with your configured root (‘/’ for Artsy).
Be sure to leave out your domain (http://artsy.net).
1 2 3 | |
We now needed a global link handler that will leverage Backbone’s navigate method which takes
care of updating the URL and avoiding a page refresh or alternatively wiring up the hashtag fallback.
Since we follow the convention of starting all href attributes with our application’s root, we
can match on that in our selector to get all anchors whose link begins with our root, a[href^='/'].
This link handler is a great place to ensure backward compatibility while #!s are removed from
internal links.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Thank you TenFarms for the excellent write up on proper link handling for pushState enabled browsers.
External Links
The application will need a small check early in the initialization process to redirect external links still expecting the #! routing scheme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
The Server
Now that our app will receive requests to full URLs ‘http://artsy.net/artwork/mattew-abbott-lobby-and-supercomputer’ instead of ‘http://artsy.net/#!/artwork/mattew-abbott-lobby-and-supercomputer’, we need to update our Rails setup.
Below is an excerpt from our Rails application’s router.
Note references to our home and artworks controllers. Both use a before filter
to determine a user’s authentication state and serve a different layout, with
unique assets or Backbone applications.
Controllers related to specific models now have the opportunity to bootstrap associated JSON or mark up and we now get expected 404 (file not found) error behavior without extra work required by a hash routing scheme.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
An added bonus here is a near one to one mapping with the Rails and client routes.
1 2 3 4 5 6 7 | |
URLs R 4 Ever
Dan Webb’s assertion that URLs are forever is correct, but so is Isaac Asimov’s statement on change. You can’t predict the future. You make decisions based on the best data you have at the time. We started our app with hashtag routing in early 2011 and added the ! around five months later (about the same time Dan Webb wrote his post). Had we started Artsy today, even six months ago, I’m confident we would have enabled Backbone’s pushState routing. There’s no need to look back. The future is here and its URLs are #! free!