Back to Dioxus

Dioxus Router Architecture

notes/architecture/09-ROUTER.md

0.7.79.3 KB
Original Source

Dioxus Router Architecture

The Dioxus router provides type-safe, declarative routing with support for nested layouts, dynamic parameters, and platform-agnostic navigation.

Route Definition

Routable Trait

Routes are defined using Rust enums with the #[derive(Routable)] macro:

rust
#[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 segments
  • render(level: usize) -> Element: Renders component at nesting level
  • FromStr implementation: Parses URLs into route enums
  • Display implementation: Converts routes to URLs
  • Helper methods: is_child_of(), parent(), static_routes()

Route Matching Priority

  1. Query routes (/?:query)
  2. Static routes (/route)
  3. Dynamic routes (/:route)
  4. Catch-all routes (/:..route)

Segment Types

  • Static: Literal path segments like /blog
  • Dynamic: Single parameters like /:id (implements FromRouteSegment)
  • CatchAll: Multiple segments like /:..segments (implements FromRouteSegments)

Query Parameters

  • Spread query: /?:..query (entire query string, uses FromQuery)
  • Segmented query: /?:param1&:param2 (individual params, use FromQueryArgument)

Hash Fragments

/#:hash_segment (uses FromHashFragment)

RouterContext

Manages navigation state with these core methods:

rust
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:

  1. Created in Router component with RouterConfig
  2. Provided via Dioxus context to children
  3. Uses signals for reactive updates
  4. Tracks subscribers for re-rendering
  5. Integrates with history provider
rust
pub enum NavigationTarget<R = String> {
    Internal(R),      // Route parsed by router
    External(String), // External URL (browser handles)
}

Declarative navigation:

rust
Link {
    to: NavigationTarget,
    active_class: Option<String>,  // Applied when matches current
    class: Option<String>,
    new_tab: bool,
    onclick: Option<EventHandler>,
    children: Element,
}

Behavior:

  1. Accesses router without subscribing
  2. Checks if target matches current for active_class
  3. On click (left button, no modifiers):
    • Prevents default
    • Routes via router.push_any(target)
    • Executes optional onclick
  4. Generates <a> tag with href for SEO
  5. External links use default browser navigation

Nested Routes

Layouts and Outlets

rust
#[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
    }
}

Outlet Component

  • Renders child routes at correct nesting depth
  • Uses OutletContext to track depth level
  • Works with layout wrapping via macro

VirtualDOM Integration

  1. Routable::render(level) matches route at nesting level
  2. Each layout increments nesting level
  3. Router changes mark context-accessing components dirty
  4. Outlet at correct depth renders matched component

Route Parameters

Parameter Traits

ComponentTraitExample
Path segmentFromRouteSegment / ToRouteSegment/users/:id
Multiple segmentsFromRouteSegments / ToRouteSegments/files/:..path
Query fullFromQuery/?:q=search
Query argFromQueryArgument/?page=1&sort=name
HashFromHashFragment/#section-id

Auto-Implementation

Traits auto-implement for types with standard traits:

  • FromRouteSegment: FromStr + Default
  • FromQueryArgument: FromStr + Default
  • FromQuery: From<&str>
  • FromHashFragment: FromStr + Default

URL Encoding

  • Path segments: PATH_ASCII_SET (excludes /, ?, #)
  • Query strings: QUERY_ASCII_SET (excludes &, =)
  • Hash fragments: FRAGMENT_ASCII_SET
  • Auto encode/decode during parse/display

Router Macro

Code Generation

#[derive(Routable)] generates three implementations:

1. FromStr Implementation:

rust
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:

rust
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:

rust
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()
        }
    }
}

Error Type Generation

Each route generates detailed error enum:

rust
pub enum RouteMatchError {
    Home(HomeParseError),
    Blog(BlogParseError),
    BlogIdParseError(ParseIntError),
}

Segment Parsing

rust
pub enum RouteSegment {
    Static(String),        // "/blog" -> Static("blog")
    Dynamic(Ident, Type),  // "/:id" -> Dynamic(id, u32)
    CatchAll(Ident, Type), // "/:..paths" -> CatchAll(paths, Vec<String>)
}

Process:

  1. Split URL into path, query, hash
  2. Remove trailing slashes
  3. Percent-decode components
  4. Split path by /
  5. Classify segments (static/dynamic/catch-all)
  6. Match field names to route parameters
  7. Validate types exist in struct fields

History Abstraction

History Trait

Platform abstraction for navigation:

rust
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;
}

MemoryHistory

In-memory fallback implementation:

rust
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:

  • Default starts at /
  • push: Adds current to history, sets new as current, clears future
  • replace: Only changes current
  • go_back/forward: Swaps between stacks
  • Does not navigate external URLs

Use Cases:

  • Testing/SSR without browser history
  • In-memory fullstack navigation
  • Development without browser

Platform Implementations

Web: Browser History API, popstate events Desktop: Platform-specific window/navigation APIs LiveView: WebSocket with server-managed state

VirtualDOM Integration

  1. Initialization:

    • Router creates RouterContext with config
    • Provides to descendants via context
    • Gets current route from history
    • Syncs with history if needed
  2. URL Change:

    • RouterContext::push_any(NavigationTarget) called
    • History provider's method invoked
    • change_route() callback fires
    • Subscribers marked dirty
  3. Re-render:

    • Components using use_router()/use_navigator() marked dirty
    • Routable::render(level) generates new tree
    • Layouts render at nesting levels
    • Outlet renders final component
  4. Parameter Passing:

    • Route parameters part of enum variant
    • Passed as component props
    • Type-safe at compile time

Key Design Decisions

  1. Trait-Based Extensibility: Custom types implement parameter traits
  2. Type Safety: Routes are enums - invalid routes are compile errors
  3. Macro-Driven: Reduces boilerplate, efficient matching
  4. Platform Abstraction: History trait enables different renderers
  5. Reactive Updates: Signals and subscriptions for efficient re-renders
  6. Nested Layouts: Outlets allow composition without complexity
  7. URL Encoding: Automatic percent-encoding/decoding
  8. SEO-Friendly: Links generate real <a> tags with hrefs