Once a router is created, the routing tree is immutable and cannot be changed. To modify the routes, we have to make a new copy of the router, with modified routes and/or options. For this, the `Router` exposes the resolved routes via `r/routes` and options via `r/options`.
Routers can be nested too, using the catch-all parameter.
A router with nested routers using a custom `:router` key:
```clj
(def router
(r/router
[["/ping" :ping]
["/olipa/*" {:name :olipa
:router (r/router
[["/olut" :olut]
["/makkara" :makkara]
["/kerran/*" {:name :kerran
:router (r/router
[["/avaruus" :avaruus]
["/ihminen" :ihminen]])}]])}]]))
```
Matching by path:
```clj
(r/match-by-path router "/olipa/kerran/iso/kala")
;#Match{:template "/olipa/*"
; :data {:name :olipa
; :router #object[reitit.core$mixed_router]}
; :result nil
; :path-params {: "kerran/iso/kala"}
; :path "/olipa/iso/kala"}
```
That not right, it should not have matched. The core routing doesn't understand anything about nesting, so it only matched against the top-level router, which gave a match for the catch-all path.
As the `Match` contains the route data, we can create a new matching function that understands our custom `:router` syntax. Here is a function that does recursive matching using the subrouters. It returns either `nil` or a vector of mathces.
In all the examples above, the routers were created ahead of time, making the whole route tree effective static. To do dynamic routing, we should use router references so that we can update the routes either on background or per request basis. Let's walk through both cases.
First, we need to modify our matching function to support router references:
A router that can be updated on demand, for example based on a domain event when a new entry in inserted into a database. We'll wrap the router into a `atom` to achieve this.
```clj
(def beer-router
(atom
(r/router
[["/lager" :lager]])))
```
Another router, which is re-created on each routing request.
```clj
(def dynamic-router
(reify clojure.lang.IDeref
(deref [_]
(r/router
["/duo" (keyword (gensym ""))]))))
```
Now we can compose the routers into a system-level static root router.
The dynamic routes are re-created on every request:
```clj
(name-path "/dynamic/duo")
; [:other :2390883]
(name-path "/dynamic/duo")
; [:other :2390893]
```
### Performance
With nested routers, instead of having to do just one route match, matching is recursive, which adds a small cost. All nested routers need to be of type catch-all at top-level, which is order of magnitude slower than fully static routes. Dynamic routes are the slowest ones, at least an order of magnitude slower, as the router needs to be recreated for each request.
Here's a quick benchmark on the recursive matches.
In this example, we could have wrapped the top-level router in an `atom` and add the beer-routes directly to it, making them order of magnitude faster.