Reitit Router in Re-frame

April 22, 2020

I've built re-frame apps for about a year now, and have always used secretary and accountant for routing. In a recent project, I looked into Reitit, a router from metosin to see how it compared. In my opinion, Reitit is well featured, supported my needs very well, and has been easier to work with.

I recently added Reitit to my Starters repo, which I introduced a few posts ago. To summarize, Starters is intended to live as a documented example for getting full-stack clojure apps setup and running with some useful features.

I used a few sources as references to get this working the first time:

Rough outline for adding the Reitit router

Not too in depth, but hopefully touches on the required pieces.

deps.edn (or shadow-cljs.edn)

These were the latest at the time of writing:

metosin/reitit {:mvn/version "0.4.2"}
metosin/reitit-malli {:mvn/version "0.4.2"}


I have not included all the code, so be sure to read the linked examples above and/or routes.cljs in the starters repo.

Subs, Events, Effects

(fn [db]
(:current-route db)))
(fn [_cofx [_ & route]]
{:navigate! route}))

I've included only the above sub and event as they are the most relevant to a consumer. These are intended to support usage throughout the app. Note that they are not tied to the namespace - you can do that if you'd like, but it's not strictly necessary.

Routes as data

The heart of the routes concept: the description of the routes as data.

These are simple definitions providing a name, view, and controllers to each route.

(def routes
{:name :routes/home
:view home/page
:link-text "Home"
[{:start (fn []
(println "Entering home page"))
:stop (fn []
(println "Leaving home page"))}]}]
{:name :routes/files
:view files/files-page
:link-text "Files"
[{:start (fn []
(println "Entering files page")
(rf/dispatch [::events/fetch-files]))
:stop (fn []
(println "Leaving files page"))}]}]
{:name :routes/file
:view files/file-page
:link-text "Files"
:coercion reitit.coercion.malli/coercion
:params {:path [:map [:id string?]]}
[{:parameters {:path [:id]}
:start (fn [{:keys [path]}]
(let [file-id (:id path)]
(println "Entering files/:id page for id" file-id)
(rf/dispatch [::events/fetch-files])
(rf/dispatch [::events/set-active-file-id file-id])))
:stop (fn []
(println "Leaving files page"))}]}]])

I've included the third definition to give exposure to coercion, which metosin offers a few solutions for. I've chosen malli-style here. For more on what coercion is, see reitit's docs.


(defn init-routes! []
(js/console.log "initializing routes")
{:use-fragment true}))

init-routes! should be called at app startup, usually somewhere in your app.core namespace.


(defn mount-root []
(reagent/render [views/root]
(.getElementById js/document "app")))

Make sure to call routes/init-routes! before rendering your root view via reagent.


A simple root component, that dispatches to the current-route's :view.

(defn main-page
(let [current-route @(rf/subscribe [:current-route])]
(when current-route
[(-> current-route :data :view)])]))
(defn root []
{:style {:width "100vw"}}

Now use it!

At this point, your app should support Reitit. How to use it?

;; in your repl
(rf/dispatch [:navigate :routes/files]))
;; example button
{:on-click #(rf/dispatch [:navigate :routes/files])
:type "button"
:value "Navigate to Files Page"}]
(let [file {:id "some-id" :name "some-filename"}]
{:on-click #(rf/dispatch [:navigate :routes/file file])
:type "button"
:value (str "Navigate to " (:name file))}])

From here

Again, the starters repo contains a fully working example if you'd like to see all the code.

I can recommend this router from experience with my latest re-frame app, but if you're feeling adventurous, you might also look into kee-frame, which comes with routing and controllers bundled in, among other things. I haven't touched it yet, but hope to take it for a spin soon.

Russell Matney

Russell Matney

Writing, Stories, Software, Indie Games


© 2020