Ember is all about your URL and keeping it up to date with your application. If you care about sharing, keeping your place, bookmarking, SEO, and more; you too should really care about your app's URLs.

However, one of the biggest issues that we run into is that by default, most Javascript browser application tools use a hash or hash bang in their URLs to maintain state. If your not sure what I mean, check out this link from an example Ember application:

It's not bad. I can relatively see that we are getting the user with an id of three (sure a slug would have been more human readable but this works). nd we can keep going back to that same url from a link like above, a bookmark, and it can even be thrown in an iframe

But, there are some downfalls to using this syntax. First is the problem of readability, while more sites are using hashes and hashbangs in their URLs, they don't have significance to your user: why should they care if the next view is loaded as part of a Single Page App or from the server? Secondly, there are issues with Google and other search engines can be picky when indexing hash and hash bang URLs. Finally, when you do want to create noscript content from the server side, most server-side frameworks don't like to read Javascript hash URLs.

So first, let's get Ember to play nice with not using the hash bang anymore, then we will get Laravel to play nice, and finally, we will make this even more robust and allowing more complex URL matches on both sides of the application.

Setting Up Ember

Luckily, with Ember, the router already has an adapter that will allow us to get rid of the hash in our URL. By using Ember.HistoryLocator as our routers location property, we can quickly get rid of the hash and also see some performance wins for changing route URLs. This is because under the hood, the Ember.HistoryLocator uses the browser's HTML history push-state API. To do this, all we need to do is add this simple bit of code after creating our application.

App.Router.reopen({
	location: 'history'
});

Using a strict version of Ember.HistoryLocator will lose support of IE <= 9, so instead you can use auto which will detect what works best for your browser.

Setting Up Laravel

So, now we've got our application URL looking more like ember-app/users/3 which is great! But, if you navigate around in the app and you refresh the page, you'll see that Laravel is throwing a route NotFoundHttpException. If you think about it for a second, this makes sense: Laravel thinks that this is just a normal server rendered page. So, let's go in the router and change that with a regular expression in our route definition. Where we used to have something like Route::get('ember-app', /* Logic here */); we will need to change this to accept anything after ember-app in our URL. We'll change that route definition to more like this:

Route::get('ember-app{data?}', /* Logic here */)->where('test', '.*');

This will make Laravel match any route starting with ember-app, including our ember-app/users/3.

Setting our RootURL

Unfortunately, we aren't quite there yet. Right now, Ember thinks we want to load App.EmberAppUserView which isn't what we want. So luckily, we can create our own locater that extends Ember.HistoryLocator and just set our RootURL which tells the router what to ignore when trying to start our router. While we could just set the rootURL manually to ember-app, that won't be too great if we want to move the application to a different route or if we have dynamic segments to our rootURL. So we'll send the generated rootURL to Javascript in our view.

// Controller
// $routeParams is array of dynamic segments
$rootURL = URL::route('route_name', $routeParams, false);
View::make('ember-app-view', compact('rootURL'));

// View
<script>
	window.rootURL = <?= $rootURL ?>;
</script>
// Load ember app scripts

Then we need to build our locater for Ember:

App.HistoryLocation = Ember.HistoryLocation.extend({
	implementation: 'custom-history',
	rootURL: window.rootURL
});

Ember.Location.registerImplementation('custom-history', App.HistoryLocation);

App.Router.reopen({
	location: 'custom-history'
});

Now we have gotten rid of our hash bang in our route even when our app isn't located at the index of our domain.