I recently replaced secretary with bidi and pushy in a re-frame project that was created fresh out of leiningen template and this is how I did it, but first, the reason.

What I like about bidi is that it’s bidirectional. It not only parses URLs into data structures but also generates URLs from data structures. This is not unique to bidi, silk also does it and feature-wise they are almost equivalent. On bidi’s website you can find this table comparing various routing libraries:

I don’t have a strong reason to chose bidi over silk, both seem like excellent choices. I also added pushy to the mix so I could have nice URLs, such as /about instead of /#/about. The reason for this is that it looks better, follows the semantics of URLs better and allows for server side rendering, to be covered in a future article. It uses HTML5 pushState which by now it’s widely supported.

Let’s get started

I’m going to use a fresh project for this article, but the information here is applicable to any similar situation. Let’s get started:

lein new re-frame projectx +routes

The first step is to get rid of secretary and include bidi and pushy by replacing

[secretary "1.2.3"]

with

[bidi "1.20.3"]
[kibu/pushy "0.3.2"]

The Routes

The routes.cljs file will be completely re-written. The namespace declaration will include bidi and pushy:

The :handler of the matched-route will be :home or :about and panel-name is constructed to be :home-panel or :about-panel so that we can dispatch setting the active panel the same way secretary used to do it. And that’s the core of it. You now have to change URLs in the view to be /about and / instead of /#/about and /#/ respectively.

Generating URLs

The whole point of using bidi was to not hard-code the URLs, for that I added this function to routes.cljs:

(def url-for (partial bidi/path-for routes))

which allowed me to replace the previous URLs with:

(routes/url-for :about)

and

(routes/url-for :home)

This works fine until you try to re-load the about page and you get a 404 Not Found error. This is because the server doesn’t know about the /about URL or any other URL the client-side might support. What you need to do is just send serve the application no matter what the URL and let the client do the dispatching (with any exceptions you might have for APIs and whatnot).

I’m actually not quite sure how you do it with a figwheel-only project, probably by setting a ring handler. With compojure I ended up creating a route like this:

(routes (ANY "*" [] (layout/render "app.html")))

Something else you might consider is passing the whole matched-route structure to the handler, so that the handler has access to path and query attributes.

And that’s it! Now you have beautiful bi-directional no-hash client-side routing.

I recently replaced secretary with silk and pushy in a re-frame project that was created fresh out of leiningen template and this is how I did it, but first, the reason.

What I like about silk is that it’s bidirectional. It not only parses URLs into data structures but also generates URLs from data structures. This is not unique to silk, bidi also does it and feature-wise they are almost equivalent. On bidi’s website you can find this table comparing various routing libraries:

I don’t have a strong reason to chose silk over bidi, both seem like excellent choices. I also added pushy to the mix so I could have nice URLs, such as /about instead of /#/about. The reason for this is that it looks better, follows the semantics of URLs better and allows for server side rendering, to be covered in a future article. It uses HTML5 pushState which by now it’s widely supported.

Let’s get started

I’m going to use a fresh project for this article, but the information here is applicable to any similar situation. Let’s get started:

lein new re-frame projectx +routes

The first step is to get rid of secretary and include silk and pushy by replacing

[secretary "1.2.3"]

with

[com.domkm/silk "0.1.1"]
[kibu/pushy "0.3.2"]

The routes

The routes.cljs file will be completely re-written. The namespace declaration will include silk and pushy:

The :name of the matched-route will be :home or :about and panel-name is constructed to be :home-panel or :about-panel so that we can dispatch setting the active panel the same way secretary used to do it.

And that’s the core of it. You now have to change URLs in the view to be /about and / instead of /#/about and /#/ respectively.

Generating URLs

The whole point of using silk was to not hard-code the URLs, for that I added this function to routes.cljs:

(def url-for (partial silk/depart routes))

which allowed me to replace the previous URLs with:

(routes/url-for :about)

and

(routes/url-for :home)

This works fine until you try to re-load the about page and you get a 404 Not Found error. This is because the server doesn’t know about the /about URL or any other URL the client-side might support. What you need to do is just send serve the application no matter what the URL and let the client do the dispatching (with any exceptions you might have for APIs and whatnot).

I’m actually not quite sure how you do it with a figwheel-only project, probably by setting a ring handler. With compojure I ended up creating a route like this:

(routes (ANY "*" [] (layout/render "app.html")))

Something else you might consider is passing the whole matched-route structure to the handler, so that the handler has access to path and query attributes.

And that’s it! Now you have beautiful bi-directional no-hash client-side routing.