Skip to content

Commit 6c895b2

Browse files
taymoor89andypfedda
authored
chore(docs): create ADR for client-side routing (#1033)
* chore(docs): create ADR for client-side routing * chore(docs): add note for Tanstack router issue * chore(docs): add examples and url-state-provider background * chore(docs): update docs/decisions/004_client_side_routing.md Co-authored-by: Esther Schmitz <edda@users.noreply.github.com> * chore(docs): fix formatting --------- Co-authored-by: Andreas Pfau <andreas.pfau@sap.com> Co-authored-by: Esther Schmitz <edda@users.noreply.github.com>
1 parent 9453d27 commit 6c895b2

1 file changed

Lines changed: 134 additions & 0 deletions

File tree

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# ADR: Unified client side routing in Juno
2+
3+
## Context
4+
5+
The application requires robust client-side routing with support for:
6+
7+
- Configurable mounting base paths so application router can work with a parent router in the shell app.
8+
- Optional hashed routing in case shell app don't want to mount child app on certain path or does not have client side routing at all.
9+
- Data preloading and caching.
10+
- URL-based state persistence.
11+
- Support for optional, UI-level state (e.g., modals or panels) without affecting navigation hierarchy.
12+
13+
We evaluated several routing libraries including React Router and TanStack Router. After evaluating feature sets, flexibility, and community support, we decided to adopt **TanStack Router**.
14+
15+
Additionally, to integrate routing with data fetching and ensure URL-driven state management, we considered various combinations of client-side state and URL parsing utilities.
16+
17+
## Decision
18+
19+
We adopted the following architecture for client-side routing and state:
20+
21+
1. **Routing Library**: We use **TanStack Router** to manage client-side routing.
22+
23+
- The router mounts to a `basePath`, provided via application props, defaulting to `'/'`.
24+
- This allows embedding the app under sub-paths without requiring changes to the router configuration.
25+
26+
2. **Hashed Routing Support**:
27+
28+
- The router can optionally use hashed history (`#` URLs) when the prop `enableHashedRouting` is set to `true`. It defaults to `false`.
29+
- This supports deployments where traditional URL rewriting is not available (e.g., GitHub Pages or an External Dashboard).
30+
31+
- > **Note:**
32+
> TanStack Router has a known [issue/behavior](https://github.com/TanStack/router/issues/4370#issuecomment-3012344925) in **hashed routing mode** where it includes query parameters from the entire URL—not just the hash fragment—when supplying the `searchString`.
33+
> If your application depends on extracting query parameters specifically from the **hash fragment**, you’ll need to handle this manually in the `parseSearch` method within your router configuration.
34+
35+
3. **Data Loading and Caching**:
36+
37+
- We use **TanStack Query** for invoking API calls and caching their results.
38+
- Apollo Client is still used for the actual GraphQL operations, but TanStack Query provides better control over caching and fetching behavior during route transitions.
39+
40+
4. **URL State Encoding/Decoding**:
41+
42+
- We utilize the `v2/encode` and `v2/decode` utilities from the `url-state-provider` package to handle query string parameters. For more information see the section below [Why we use `v2/encode` and `v2/decode` from url-state-provider](#why-we-use-v2encode-and-v2decode-from-url-state-provider).
43+
- Applications may persist their state in the URL, as long as:
44+
- The state can be serialized by the `encode` utility.
45+
- The state can be correctly interpreted back after `decode` utility is applied.
46+
47+
5. **Route Definitions and Optional UI State**:
48+
49+
- Routes are defined using TanStack Router's file-based routing to reflect the app’s core navigational paths.
50+
- To control **optional UI elements** such as panels, modals, or drawers (that can appear conditionally on a given page), we use **URL search parameters** rather than path parameters.
51+
- This ensures that opening or closing optional UI components does **not trigger full route transitions** or reload data unnecessarily.
52+
- It also preserves the logical page hierarchy and improves back/forward navigation behavior in the browser.
53+
54+
#### Examples
55+
56+
Following are the URLs created from following file structure
57+
`/products -> index.html`
58+
59+
##### Basic Route
60+
61+
- `/products`
62+
Loads the main **Products** page.
63+
64+
- `/products/product-a`
65+
Loads the page for **Product A**
66+
67+
##### Optional UI Elements via Search Params
68+
69+
- `/products?overview=product-a`
70+
Shows quick overview of the production in the form of, say, panel.
71+
72+
- `/products?sort=desc`
73+
Sorts list of products in descending order.
74+
75+
- `/products?f_availability=germany,uk`
76+
Applies availability filter where a product is either available in Germany or UK
77+
78+
## Consequences
79+
80+
- **Pros**:
81+
82+
- Flexible routing that supports multiple deployment environments, Shell App(with or without router), External Dashboards.
83+
- Improved control over API call timing and caching via TanStack Query.
84+
- Clear separation of concerns between routing, data loading, and state persistence.
85+
- Clean URL patterns that distinguish between core navigation and optional UI state.
86+
- Enhanced browser UX with predictable back/forward behavior for panels and modals.
87+
88+
- **Cons**:
89+
- Increases the learning curve slightly due to the use of newer libraries (TanStack Router).
90+
- The encode/decode contract must be strictly followed to prevent inconsistent behavior.
91+
92+
## Why we use `v2/encode` and `v2/decode` from url-state-provider
93+
94+
Recently, we introduced v2 of the encoding/decoding utilities in `url-state-provider`. These utilities are essentially thin wrappers around the `query-string` library (which is also referenced in the official TanStack Router docs). By default, TanStack uses `JSON.stringify` for URL serialization.
95+
96+
### Example: URL Search Params Serialization
97+
98+
Consider the following state object:
99+
100+
```js
101+
{
102+
filter: ['active', 'pending'],
103+
sort: 'date',
104+
page: 2
105+
}
106+
```
107+
108+
**Serialized with `query-string` (used by `v2/encode`):**
109+
110+
```
111+
/dashboard?filter=active,pending&sort=date&page=2
112+
```
113+
114+
**Serialized with `JSON.stringify` (TanStack Router default):**
115+
116+
```
117+
/dashboard?state=%7B%22filter%22%3A%5B%22active%22%2C%22pending%22%5D%2C%22sort%22%3A%22date%22%2C%22page%22%3A2%7D
118+
```
119+
120+
> The `query-string` approach produces a flatter, more human-readable URL, while `JSON.stringify` encodes the entire state as a single opaque string.
121+
122+
You might wonder: If we can use `query-string` directly with TanStack Router, why expose it through `url-state-provider`?
123+
124+
Here are the key reasons:
125+
126+
- **Consistent flat structure:** We want application developers to persist state to the URL in a flat format—not deeply nested. Arrays of primitive values are allowed, but complex nested collections are not.
127+
- **Improved readability:** The `query-string` library provides a more human-readable URL format compared to the default `JSON.stringify` output. For example, we can put arrays of strings in a nicely comma-separated form in the URL, which is human readable (filters are a very good use-case of it).
128+
- **Centralized control:** Exposing the utility through `url-state-provider` lets us control the `query-string` version and apply sensible, consistent defaults across the app.
129+
130+
By using `v2/encode` and `v2/decode`, we ensure that our application's URL state is robust, predictable, and easy to maintain, even as requirements evolve.
131+
132+
## Notes
133+
134+
- Developers must validate state objects before encoding to prevent invalid URL states.

0 commit comments

Comments
 (0)