notes/architecture/09-ROUTER.md
The Dioxus router provides type-safe, declarative routing with support for nested layouts, dynamic parameters, and platform-agnostic navigation.
Routes are defined using Rust enums with the #[derive(Routable)] macro:
#[derive(Routable, Clone, PartialEq, Debug)]
enum Route {
#[route("/")]
Home {},
#[route("/blog/:blog_id")]
Blog { blog_id: usize },
#[route("/edit?:blog_id")]
Edit { blog_id: usize },
#[route("/hashtag/#:hash")]
Hash { hash: String },
}
The Routable trait provides:
const SITE_MAP: Static map of all route segmentsrender(level: usize) -> Element: Renders component at nesting levelFromStr implementation: Parses URLs into route enumsDisplay implementation: Converts routes to URLsis_child_of(), parent(), static_routes()/?:query)/route)/:route)/:..route)/blog/:id (implements FromRouteSegment)/:..segments (implements FromRouteSegments)/?:..query (entire query string, uses FromQuery)/?:param1&:param2 (individual params, use FromQueryArgument)/#:hash_segment (uses FromHashFragment)
Manages navigation state with these core methods:
impl RouterContext {
// Navigation
pub fn push(&self, target: impl Into<NavigationTarget>);
pub fn replace(&self, target: impl Into<NavigationTarget>);
// History
pub fn go_back(&self);
pub fn go_forward(&self);
pub fn can_go_back(&self) -> bool;
pub fn can_go_forward(&self) -> bool;
// Introspection
pub fn current<R: Routable>(&self) -> R;
pub fn full_route_string(&self) -> String;
pub fn prefix(&self) -> Option<String>;
// Site map
pub fn site_map(&self) -> &'static [SiteMapSegment];
}
The router context is:
Router component with RouterConfigpub enum NavigationTarget<R = String> {
Internal(R), // Route parsed by router
External(String), // External URL (browser handles)
}
Declarative navigation:
Link {
to: NavigationTarget,
active_class: Option<String>, // Applied when matches current
class: Option<String>,
new_tab: bool,
onclick: Option<EventHandler>,
children: Element,
}
Behavior:
active_classrouter.push_any(target)<a> tag with href for SEO#[derive(Routable)]
enum Route {
#[nest("/admin")]
#[layout(AdminLayout)]
#[route("/")]
AdminHome {},
#[route("/users")]
Users {},
#[end_layout]
#[end_nest]
#[route("/")]
Home {},
}
#[component]
fn AdminLayout() -> Element {
rsx! {
header { "Admin Panel" }
Outlet::<Route> {} // Children render here
}
}
OutletContext to track depth levelRoutable::render(level) matches route at nesting level| Component | Trait | Example |
|---|---|---|
| Path segment | FromRouteSegment / ToRouteSegment | /users/:id |
| Multiple segments | FromRouteSegments / ToRouteSegments | /files/:..path |
| Query full | FromQuery | /?:q=search |
| Query arg | FromQueryArgument | /?page=1&sort=name |
| Hash | FromHashFragment | /#section-id |
Traits auto-implement for types with standard traits:
FromRouteSegment: FromStr + DefaultFromQueryArgument: FromStr + DefaultFromQuery: From<&str>FromHashFragment: FromStr + DefaultPATH_ASCII_SET (excludes /, ?, #)QUERY_ASCII_SET (excludes &, =)FRAGMENT_ASCII_SET#[derive(Routable)] generates three implementations:
1. FromStr Implementation:
impl std::str::FromStr for Route {
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Split URL into path, query, hash
// Percent-decode segments
// Try matching against all routes (specificity order)
// Return error with attempted routes if no match
}
}
2. Display Implementation:
impl std::fmt::Display for Route {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Route::Blog { blog_id } => {
write!(f, "/blog/{}", blog_id)?;
// Percent-encode as needed
}
}
}
}
3. Routable Trait Implementation:
impl Routable for Route {
const SITE_MAP: &'static [SiteMapSegment] = &[...];
fn render(&self, level: usize) -> Element {
match (level, self) {
(0, Self::Blog { .. }) => rsx! { Layout { Outlet {} } }
(1, Self::Blog { blog_id }) => rsx! { Blog { blog_id: *blog_id } }
_ => VNode::empty()
}
}
}
Each route generates detailed error enum:
pub enum RouteMatchError {
Home(HomeParseError),
Blog(BlogParseError),
BlogIdParseError(ParseIntError),
}
pub enum RouteSegment {
Static(String), // "/blog" -> Static("blog")
Dynamic(Ident, Type), // "/:id" -> Dynamic(id, u32)
CatchAll(Ident, Type), // "/:..paths" -> CatchAll(paths, Vec<String>)
}
Process:
/Platform abstraction for navigation:
pub trait History {
fn current_route(&self) -> String;
fn current_prefix(&self) -> Option<String>;
fn can_go_back(&self) -> bool;
fn can_go_forward(&self) -> bool;
fn go_back(&self);
fn go_forward(&self);
fn push(&self, route: String);
fn replace(&self, path: String);
fn external(&self, url: String) -> bool;
fn updater(&self, callback: Arc<dyn Fn() + Send + Sync>);
fn include_prevent_default(&self) -> bool;
}
In-memory fallback implementation:
pub struct MemoryHistory {
state: RefCell<MemoryHistoryState>,
base_path: Option<String>,
}
struct MemoryHistoryState {
current: String,
history: Vec<String>, // For back
future: Vec<String>, // For forward
}
Behavior:
/push: Adds current to history, sets new as current, clears futurereplace: Only changes currentgo_back/forward: Swaps between stacksUse Cases:
Web: Browser History API, popstate events Desktop: Platform-specific window/navigation APIs LiveView: WebSocket with server-managed state
Initialization:
Router creates RouterContext with configURL Change:
RouterContext::push_any(NavigationTarget) calledchange_route() callback firesRe-render:
use_router()/use_navigator() marked dirtyRoutable::render(level) generates new treeOutlet renders final componentParameter Passing:
<a> tags with hrefs