Framework Routers and Linking
One of the most common uses of a JavaScript framework is creating Single Page Apps, and the central feature required to do this effectively is a router. All modern frameworks have one of these, and for the most part they operate similarly.
With that in mind I got curious recently about how different frameworks implement their routing logic. What I found was a surprising variety of implementations, which reveal quite a lot about the way the frameworks are structured and the intentions behind them. The frameworks I investigated were what I consider to be the most common and modern frameworks in JavaScript at this time: Angular 2, Ember, React, Vue, Meteor and Aurelia.
Each of these frameworks has a sizable and vocal following. Further discussion will focus entirely on the syntax and structure of the router alone, and shouldn’t be taken as a criticism or praise of the framework in its entirety. It should also go without saying that the following is largely subjective. I’m not an expert on all of these frameworks, and am attempting to take an open and honest look at these features based on their official documentation and some supplementary information. Every attempt has been made to provide well-intentioned and accurate information, but if features are not discussed correctly, please feel free to comment accordingly and the record will be corrected.
The same site structure is to be replicated in all routers — a site index, a static “about” page, a posts list page, and a page for an individual post. Ideally the post should be a child or nested route of the posts, though this is not universally supported. Every effort has been made to make the code represent idiomatic usage of the framework, and various bits of boilerplate and setup are omitted.
I want to acknowledge up front that my chief experience is as an Ember developer, and this article is written from that basis. The intention here is to share my learning around a range of technologies, not to engage in endless debate about framework specifics.

The Router
// posts
export const PostRoutes: RouterConfig = [{
path: '/posts',
component: PostListComponent,
children: [
{ path: ':id', component: PostComponent },
{ path: '', component: PostListComponent }
]
}];
Angular’s router syntax is very clear. It’s TypeScript, which adds a bit of unique flavour, but it’s clear and readable. The only thing that complicates or undermines the router is that recommended usage seems to be to have pieces of RouterConfig for app sections scattered throughout the app. Having the site’s entire route structure visible upfront can be helpful to get an understanding and overview. Whether these should be managed centrally or separated to areas of their concern is pretty subjective and not really worthy of criticism. Like many framework choices this one is very much open to interpretation.
Simple Links
<a routerLink="/about">About</a>
<a routerLink="/posts">Posts</a>
Router links are clear as well, with custom attributes to handle the actual path. This is a not uncommon solution. It’s simple and short, without a lot of extra fluff added.
Dynamic Links
<li *ngFor="let post of posts">
<a [routerLink]="['/posts', post.id]">{{post.title}}</a>
</li>
Unfortunately it’s at this point that Angular 2 descends into special syntax soup. Let me just say subjectively and completely as a matter of opinion: this is gross. I know there are reasons and meaning and I know that’s how Angular has always operated, but… ew.
On a more objective note, this is actually not supported by default. Not all components can be routed like this off the bat. They need to be set up as an ActivatedRoute through the linked component’s constructor, and the linking component needs to have a shouty directive added to it as well. Why this isn’t default behaviour is something of a mystery and I’m sincerely concerned I’ve missed something.
I get the feeling that if I’m wrong, someone will let me know…

The Router
FlowRouter.route(‘/’, {
name: ‘App.home’,
action() {
BlazeLayout.render(‘App_body’, {main: ‘app_rootRedirector’ });
}
});FlowRouter.route(‘/about’, {
name: ‘About’,
action() {
BlazeLayout.render(‘App_body’, {main: ‘About_page’});
});
});FlowRouter.route('/posts', {
name: 'Posts.list',
action() {
BlazeLayout.render('App_body', { main: 'Post_list_page' });
}
});FlowRouter.route('/posts/:_id', {
name: 'Posts.show',
action() {
BlazeLayout.render('App_body', { main: 'Lists_show_page' });
}
});Meteor is unusual in that there isn’t a single router. There are two. I initially focused on Iron Router because my reading suggested it was the “default” for Meteor. However, Meteor’s official guides use Flow, so I’ve changed tracks. There seems to be a lot of discussion back and forth about which one to use. I have to say in advance I count this against Meteor. This is exactly the sort of decision I don’t want to be making in a framework.
I also have to admit, and this is highly subjective, that I don’t like Meteor’s structure on this. It feels like each FlowRouter.route() is doing way too much. Most of all this reminds me of the basic implementation of routing in the Laravel framework.
Route::get('/posts', function () {
return Posts::all();
});However, that usage is only really intended as a naive illustration or a specific case. In actual usage on any reasonable system you would link to a controller@method structure much more often. Meteor doesn’t seem to have that potential for improvement. Note that the competing Iron Router doesn’t actually change this. Its structure is the same.
Note also that there is no nesting in the posts route, no programmatic logical connection between that route and its children.
Of all the frameworks, Meteor’s router has been the hardest to come to grips with. Finding documentation of the cases I wanted to cover, which seemed very reasonable, was not simple.
And perhaps I’m out of line, but the casing and structure seem to be all over the shop, and pretty hard to like. It might just be a poorly written example, but if so it’s a poorly written example from the official docs.
Simple Links
<a href="{{pathFor route='About'}}">About</a>
<a href="{{pathFor route='Posts.list'}}">Posts</a>Though the usage of a simple handwritten url would be perfectly valid, this is considered more maintainable. Unfortunately, the syntax shown above isn’t included by default, and needs to be installed. This seems like a surprising omission. Meteor’s syntax for this should be familiar to many - it uses Spacebars, a variant of Handlebars.
Dynamic Links
{{#each list in lists}}
<a href="{{pathFor ‘Lists.show’ _id=list._id}}">{{list.name}}</a>
{{/each}}Again, this is Handlebars style syntax, which some will love and some will hate. And again, this pathFor is not part of a default Meteor install. I’d be interested to see if the pathFor binding is doing any more than string generation, or if it actually facilitates any sort of watcher on the link. But that’s out of the scope of this article.

The Router
Router.map(function() {
this.route('about');
this.route('posts', function() {
this.route('show', {path: ':post_id'});
});
});Ember’s router is impressive in its brevity. Ember has a rather unique object entity called the Route. This is simply the object connected to any given URL pattern, and handles lifecycle hooks, model data, etc. Ember has extremely rigid naming conventions that enforce things like template names and project structure, so in this case, for example, there is a Route that lives in app/routes/about.js. Because this must be the file backing this url there’s no need to specify.
This implied nature is a blessing for those who know Ember’s conventions and requirements, but may well be a burden on people who don’t. There’s no explicit statement about what method or module handles the about route, where it gets its data from, or how it functions. There is no explicit statement about what template is used.
Simple Links
{{#link-to 'about'}}About{{/link-to}}
{{#link-to 'posts'}}Posts{{/link-to}}The syntax for links is similar to Meteor, again due to the Handlebars-derived templating system. This is the “link-to helper”. This differs wildly from the land of JSX or component-included templates, but some people prefer a more separated strict MVC pattern.
Ember’s draconian conventions, strong opinions and forced separation of concerns is either its biggest benefit or a major flaw.
Dynamic Links
{{#each model as post}}
<li>{{#link-to 'posts.show' post}}posts{{/link-to}}</li>
{{/each}}Again, highly subjective whether this is clean or not (I like it) but there is a more objective benefit. In this case we’ve passed the post object through to the link-to helper. Because Ember now has a model for that route it does not need to run the model hook when it makes that transition. This means the initial XHR call to get the posts collection will be the first and last one that needs to be made. A nice default behaviour I believe not supported by the other frameworks without explicit setting and state management.

The Router
render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="about" component={About} />
<Route path="posts" component={PostList}>
<Route path=":id" component={PostShow}>
</Route>
</Route>
</Router>
), document.body)
There’s no way I can find to say this objectively. I think JSX is awful. I think this is gross. It violates the principles of good software development I’ve used all my career, and IMO just looks plain ugly. Everything about this makes me cringe. React is a fantastic library but this XML nonsense is weird. There is an alternative syntax that will bind down to JSX later, but I wanted to stick with idiomatic standards, and this is it. May God have mercy on its soul.
It’s also a mystery to me why what is essentially internal config has to “render”. And even more mysterious why config needs to render on a specified DOM element.
Simple Links
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to"/posts">Posts</Link></li>
</ul>
Atypically, but not uniquely, React replaces out the entire anchor tag with a custom one. Again, not much to say about this.
Dynamic Links
{this.state.posts.map(user => (
<li key={post.id}>
<Link to={`/posts/${post.id}`}>{post.title}</Link>
</li>
))}Much like Angular 2’s example, the clarity of this gets a little bit lost in slightly esoteric syntax. Though in fairness it’s more ES6 than it is React. This is solid use of ES6 template literals. It’s obvious enough at a glance what it’s going for. It may not be everyone’s cup of tea, but it’s clear and contemporary.

The Router
router.map({
'/': {component: HomePage},
'/about': {name: 'about', component: About},
'/posts': {
name: 'postList',
component: PostList,
subRoutes: {
'/:postId': {name: 'post', component: ShowPost}
}
}
})Vue’s router is clean and easy to read. It does a nice job of effectively tying together a route with an object that contains the stuff it needs, particularly a component. Like Angular 2 it is explicitly nesting the subroutes, which is a definite preference.
The example I’ve shown is a simple style, but Vue can also be set up to lazy-load components from its routes. However, this is largely based on Webpack’s code splitting functionality.
Simple Links
<a v-link="{ path: '/about' }">About</a>
<a v-link="{ path: '/posts' }">Posts</a>Again not unlike Angular’s approach, Vue uses a custom attribute to control the route link.
Dynamic Links
<li v-for="post in posts">
<a v-link="{ name: 'post', params: { postId: post.id }}”>{{post.title}}</a>
</li>
The Angular is strong with this one, too, particularly in the syntax of the loop itself. The structure of the link makes perfect sense thanks to the use of the named route. Note that the same could have been done with the simple links above, using a name instead of a path.

The Router
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'about', name: 'about', moduleId: 'about/index', nav: true },
{ route: 'posts', name: 'postList', moduleId: 'post/index', nav: true },
{ route: 'posts/:postId', name: 'postShow', moduleId: 'post/show' },
]);
This is pretty decent. Only two things aren’t obvious. First of all, what’s the moduleId? None of the examples or documentation actually explain it. It’s taken for granted in Aurelia Land and used without explanation. A substantial amount of searching turns out that it’s specifically a view/viewmodel pairing.
This is the only framework that did not support explicit nesting. You can see the postList and postShow routes are on the same level. Objectively that doesn’t make a huge difference, but it can remove some optimisation as well as making the structure a little less clear.
Of note is the nav: true, which I left in place despite being out of scope. This key tells Aurelia what the top level navigation elements will be, and facilitates this capability:
<li repeat.for="row of router.navigation">
<a href.bind="row.href">${row.title}</a>
</li>
Quite a nice piece of default behaviour out of the box.
Simple Links
<a route-href="route: about">About</a>
<a route-href="route: posts">Posts</a>
Aurelia’s routing is nice and clean. It explicitly states some of the things Ember users have to infer. But the linking process is oddly messy and unclear. Should you use href.bind? Or route-href? The latter seems typically preferred, with the former being used for internally generated links.
Dynamic Links
<li repeat.for="post of posts">
<a route-href="route: post, params.bind: { postId: post.id }”>${post.title}</a>
</li>
This is very similar to the Vue example shown above. In fact I copy and pasted the Vue example and just changed the syntax. To be honest, that was the only way I was able to do so. I’ve pieced this syntax together from disparate examples, as the documentation doesn’t do a particularly great job of covering it.
Conclusion
There seem to be three basic patterns in terms of the syntax of routers. The default implementation is to map a string (or string pattern) to a given component. This pattern is followed by Angular 2, React, Vue, and Aurelia (through a moduleId).
The two exceptions are Meteor and Ember. Meteor’s idiomatic method appears to run through a closure, which then explicitly sets template and data. That’s discussed in more detail in the Meteor-specific section. Ember is the other drastic variation. Its structure is impressively terse because of its reliance on its rigid conventions. But developers less familiar with those conventions will struggle with the lack of explicit detail — they can see that the ‘about’ route is supported, but not how.
Looking at these examples it’s immediately apparent the impact Angular had on the landscape. As well as the obvious of Angular 2, Aurelia and Vue show pretty clear signs of its influence, especially in the <li repeat.for> style syntax. Ember and Meteor look superficially similar, but that’s just because of their use of near-identical templating engines. React is very much forging its own path as ever.
To summarize: Ember as always requires knowledge about its structure and conventions. React is not bad but JSX is an affront to God and Man. Aurelia looks good and works well, but its documentation is deeply lacking and I’m surprised it doesn’t nest routes. Angular 2's route structure is excellent but its templating is a nicely documented garbage fire. Vue was a standout, a clear and expressive router with a range of solid features. Its documentation is excellent and its templating, though rather “angularish” is well implemented and capable. Meteor (to my great surprise) stood out for the opposite reasons. With two competing routers potentially capable of operating in two different contexts (server and client), iffy documentation, minimal example code, and a structure that doesn’t seem very maintainable. Even its use of handlebars templates are marred by ugly conventions.