Migrating from Iron Router to FlowRouter (with Blaze)


I just migrated a small Meteor + Blaze app from Iron Router to FlowRouter. Here are the main differences I found and how to work around them.

Some history

“Routing” is the feature of a web framework that displays a particular state of the UI based on the path in the URL. Basically this means choosing which templates get rendered.

In modern web frameworks like React, Angular or Meteor this is no longer something that happens on the server, it’s done in the browser by JavaScript, without loading a new page (though the user usually wouldn’t notice that detail).

Surprisingly, Meteor has never had routing as a core feature. The community stepped in at a very early stage in the framework’s development and created a couple of router projects which eventually coalesced into Iron Router (IR), a very robust and full-featured router that became the de-facto standard.

Recently, however, a new router with a more minimal design philosphy has appeared, FlowRouter by Arunoda and the amazing people at Kadira. FlowRouter is being very actively developed, is very well documented, and is gaining popularity quickly.

It’s not yet clear which router we’ll be using a year from now, (though if you have an opinion share it here), and a lot of Meteor packages are now doing work to support both, but here’s what you’ll need to know if you want to migrate from Iron Router to FlowRouter with Blaze.

What’s the difference?

In general FlowRouter has a very minimal philosophy, but here are the main differences with Iron Router and some strategies to work around them when migrating an app from IR to FlowRouter:

No rendering or layout management?

FlowRouter has a very modular design so that it’s not tightly coupled to Blaze. It actually doesn’t render templates. But there’s a separate package for that.

Solution: If you’re using Blaze you’ll want to include the kadira:blaze-layout package.

There’s no special support for a “layout” (a common view for the app, which usually contains a header, footer and some dynamic content), but you can use Meteor’s {{Template.dynamic}} to achieve the same thing. Here’s an example.

No subscription management

Technically it does have basic subscription management but it’s being removed in the next version and they don’t recommend it so let’s not talk about it here.

Solution: You’ll want to use template-level subscriptions. This will probably help you keep your templates less tightly coupled to routes anyway. (Though I would like a friendlier way of setting them up, the whole autorun in onCreated pattern is way too verbose for me).

I recommend refactoring your app to use template-level subscriptions as the first step in your migration, before removing Iron Router. Remember that multiple templates can subscribe to the same publication, you won’t get duplicate data. If you use the same template on different routes with different subscriptions you might need to nest it inside parent templates which manage the subscriptions.

No data context

With Iron Router you can directly set the data context for the template that you’re rendering. With FlowRouter you can’t.

This has a couple of effects.

First, without a data context you must define helpers for your data, which is a bit more verbose.

Second, this limits the reusability of templates. Data context is how you create reusable “components” in Blaze (whether you like it or not). Using the same template with a different data context allows you to use that template in different places in your app, being passed data and not hard-coding itself to things like the route.

For example, say you have a template that shows a user name and profile picture, given a user’s id as the data context.

In Iron Router you could have used that same template as the main template for a route (by giving it data context that IR pulls from a route parameter) and then re-use that same template elsewhere (e.g. inside an {{#each}} loop to show a list of users).

But with FlowRouter you would need to have a helper which calls FlowRouter.getParam('userId'), effectively hard-coding it to be used only with that route You can no longer use it elsewhere by giving it a different data context.

Solution: You can create a top-level empty template which has a helper that gets the data, and then pass that data to your reusable template. Unfortunately this is a lot more verbose.

Hooks don’t re-run reactively

FlowRouter limits reactive data sources to a single run so that before/after hooks (called triggers in FlowRouter) are never re-run.

The philosophy is that reactive content should be handled within the template.

Solution: Most people found Iron Router’s before/after hooks re-running reactively surprising anyway, and it was a frequent source of bugs such as duplicate analytics data. If you’re updating something on the page in a before/after hook consider doing it from a helper, otherwise an autorun that you set up in the template onCreated might do the trick.

API designed to minimize reactive re-rendering

They describe this as router.current() is evil.

The issue is “fine-grained” vs “coarse-grained” reactivity:

…In the Deps system of transparent reactivity, retrieving data is the same as subscribing to change notifications for that data … Well-designed reactive APIs support fine-grained queries. They let you ask for just the data that you need, like the family name of a Person object without the other fields.

In Iron Router if you have the route /apps/:appId/:section and you want to use the route parameter appId you need to do the following: Router.current().params.appId. In a reactive function (e.g. a helper) this causes the function to be re-run if anything about the route changes, when all we care about are changes to the one param appId.

FlowRouter reduces unnecessary re-rendering by providing fine-grained access to just the data you need, e.g. FlowRouter.getParam('appId'). If other parameters change it won’t trigger a re-run.

Solution: Well there’s no “problem” here, you’ll just change Router.current().whatever to one of the fine-grained functions like FlowRouter.getParam(paramName) or FlowRouter.getRouteName().

No pathFor helper

With Iron Router, in templates you can easily generate the URL for any route with the global pathFor helper.

FlowRouter has FlowRouter.path(…), but it’s not a template helper.

Solution: The package arillo:flow-router-helpers provides this and more. (Thanks to commenters for pointing this out!)

Summary

The future of routing in Meteor isn’t clear (though there’s a poll going on here), and it might not be trivial to switch an existing app at the moment so I’m definitely not recommending that you do it. But if you do need to then hopefully the above tips helped.

Let's stay connected. Join our monthly newsletter to receive updates on events, training, and helpful articles from our team.