Skip to main content
Freya provides a powerful, type-safe routing system through the freya-router crate. Build single-page applications with multiple views, nested layouts, and programmatic navigation.

Quick Start

Define routes using an enum with the Routable derive macro:
use freya::prelude::*;
use freya::router::*;

fn main() {
    launch(LaunchConfig::new().with_window(WindowConfig::new(app)))
}

fn app() -> impl IntoElement {
    Router::<Route>::new(|| RouterConfig::default().with_initial_path(Route::Home))
}

#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
pub enum Route {
    #[layout(Layout)]
        #[route("/")]
        Home,
        #[route("/settings")]
        Settings,
}

Defining Routes

Route Enum

Routes are defined as enum variants with the #[route] attribute:
#[derive(Routable, Clone, PartialEq)]
pub enum Route {
    #[route("/")]
    Home,
    
    #[route("/about")]
    About,
    
    #[route("/users/:id")]
    UserProfile { id: String },
    
    #[route("/posts/:post_id/comments/:comment_id")]
    PostComment { post_id: String, comment_id: String },
}

Route Parameters

Capture dynamic segments with :param syntax:
#[derive(Routable, Clone, PartialEq)]
pub enum Route {
    // Captures user_id from URL
    #[route("/users/:user_id")]
    UserDetail { user_id: String },
    
    // Multiple parameters
    #[route("/blog/:year/:month/:slug")]
    BlogPost { year: u32, month: u32, slug: String },
}
Parameters must implement FromStr and Display.

Layouts

Adding Layouts

Layouts wrap multiple routes with shared UI:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
pub enum Route {
    #[layout(AppLayout)]
        #[route("/")]
        Home,
        #[route("/about")]
        About,
}

#[derive(PartialEq)]
struct AppLayout;

impl Component for AppLayout {
    fn render(&self) -> impl IntoElement {
        rect()
            .expanded()
            .child(
                // Header/navigation
                rect()
                    .horizontal()
                    .child(Link::new(Route::Home).child("Home"))
                    .child(Link::new(Route::About).child("About"))
            )
            .child(
                // Child routes render here
                Outlet::<Route>::new()
            )
    }
}

Nested Layouts

Create complex hierarchies with nested routes:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
pub enum Route {
    #[layout(AppLayout)]
        #[route("/")]
        Home,
        
        #[nest("/users/:user_id")]
            #[layout(UserLayout)]
                #[route("/")]
                UserDetail { user_id: String },
                #[route("/posts")]
                UserPosts { user_id: String },
}

#[derive(PartialEq)]
struct UserLayout {
    user_id: String,
}

impl Component for UserLayout {
    fn render(&self) -> impl IntoElement {
        rect()
            .child(format!("User: {}", self.user_id))
            .child(
                rect()
                    .horizontal()
                    .child(
                        Link::new(Route::UserDetail {
                            user_id: self.user_id.clone()
                        })
                        .child("Details")
                    )
                    .child(
                        Link::new(Route::UserPosts {
                            user_id: self.user_id.clone()
                        })
                        .child("Posts")
                    )
            )
            .child(Outlet::<Route>::new())
    }
}

Route Components

Each route variant needs a corresponding component:
#[derive(PartialEq)]
struct Home;

impl Component for Home {
    fn render(&self) -> impl IntoElement {
        rect().child("Welcome to the home page")
    }
}

#[derive(PartialEq)]
struct UserProfile {
    id: String,
}

impl Component for UserProfile {
    fn render(&self) -> impl IntoElement {
        rect().child(format!("User Profile: {}", self.id))
    }
}
Navigate declaratively with the Link component:
use freya::router::Link;

Link::new(Route::About).child("Go to About")

// With dynamic parameters
Link::new(Route::UserProfile { id: "123".to_string() })
    .child("View Profile")

Programmatic Navigation

Use RouterContext for imperative navigation:
use freya::router::RouterContext;

Button::new()
    .on_press(|_| {
        RouterContext::get().push(Route::Settings);
    })
    .child("Go to Settings")
let router = RouterContext::get();

// Navigate to route (adds to history)
router.push(Route::Home);

// Replace current route (no history entry)
router.replace(Route::Home);

// Go back one step
router.go_back();

// Go forward one step  
router.go_forward();

// Check if can navigate
let can_go_back = router.can_go_back();
let can_go_forward = router.can_go_forward();

NativeRouter

Wrap content to enable native-style navigation handling:
NativeRouter::new().child(
    rect()
        .child(Link::new(Route::Home).child("Home"))
        .child(Outlet::<Route>::new())
)

ActivableRoute

Highlight active routes:
ActivableRoute::new(
    Route::Home,
    Link::new(Route::Home).child(Button::new().flat().child("Home")),
)
.exact(true) // Exact match only

Router Configuration

Initial Route

Router::<Route>::new(|| {
    RouterConfig::default()
        .with_initial_path(Route::Settings)
})

History Management

Use custom history implementations:
use freya::router::MemoryHistory;

RouterConfig::default()
    .with_history(MemoryHistory::default())

Hooks

use_route

Get the current route:
use freya::router::use_route;

fn app() -> impl IntoElement {
    let route = use_route::<Route>();
    
    rect().child(match route {
        Route::Home => "Home Page",
        Route::About => "About Page",
        _ => "Unknown",
    })
}

External Navigation

Handle external URLs:
use freya::router::NavigationTarget;

// External link
Link::new(NavigationTarget::<Route>::External(
    "https://example.com".to_string()
))
.child("External Link")

// Internal route
Link::new(NavigationTarget::Internal(Route::Home))
    .child("Home")

Advanced Patterns

Guarded Routes

Implement route guards with conditional rendering:
#[derive(PartialEq)]
struct ProtectedRoute;

impl Component for ProtectedRoute {
    fn render(&self) -> impl IntoElement {
        let is_authenticated = use_state(|| false);
        
        if *is_authenticated.read() {
            rect().child("Protected content")
        } else {
            rect().child(
                Link::new(Route::Login).child("Please login")
            )
        }
    }
}

Query Parameters

Implement custom query parameter parsing:
#[derive(Routable, Clone, PartialEq)]
pub enum Route {
    #[route("/search")]
    Search,
}

// Parse query manually from RouterContext
let router = RouterContext::get();
let current_url = router.current().to_string();
// Parse query string from current_url

Catch-All Routes

Handle 404 pages:
#[derive(Routable, Clone, PartialEq)]
pub enum Route {
    #[route("/")]
    Home,
    
    // Catch all other routes
    #[route("/:..segments")]
    NotFound { segments: Vec<String> },
}

#[derive(PartialEq)]
struct NotFound {
    segments: Vec<String>,
}

impl Component for NotFound {
    fn render(&self) -> impl IntoElement {
        rect()
            .child("404 - Page Not Found")
            .child(format!("Path: /{}", self.segments.join("/")))
    }
}

Complete Example

use freya::prelude::*;
use freya::router::*;

fn main() {
    launch(LaunchConfig::new().with_window(WindowConfig::new(app)))
}

fn app() -> impl IntoElement {
    Router::<Route>::new(|| RouterConfig::default().with_initial_path(Route::Home))
}

#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
pub enum Route {
    #[layout(AppLayout)]
        #[route("/")]
        Home,
        #[route("/about")]
        About,
        #[nest("/users/:user_id")]
            #[layout(UserLayout)]
                #[route("/")]
                UserDetail { user_id: String },
                #[route("/posts")]
                UserPosts { user_id: String },
}

#[derive(PartialEq)]
struct AppLayout;

impl Component for AppLayout {
    fn render(&self) -> impl IntoElement {
        NativeRouter::new().child(
            rect()
                .content(Content::flex())
                .child(
                    rect()
                        .horizontal()
                        .height(Size::px(50.))
                        .background((230, 230, 230))
                        .padding(12.)
                        .spacing(12.)
                        .child(
                            ActivableRoute::new(
                                Route::Home,
                                Link::new(Route::Home).child(Button::new().flat().child("Home")),
                            )
                            .exact(true)
                        )
                        .child(
                            ActivableRoute::new(
                                Route::About,
                                Link::new(Route::About).child(Button::new().flat().child("About")),
                            )
                            .exact(true)
                        )
                        .child(rect().width(Size::flex(1.)))
                        .child(
                            Button::new()
                                .flat()
                                .on_press(|_| RouterContext::get().go_back())
                                .child("Back")
                        )
                )
                .child(
                    rect()
                        .expanded()
                        .padding(12.)
                        .child(Outlet::<Route>::new())
                )
        )
    }
}

#[derive(PartialEq)]
struct UserLayout {
    user_id: String,
}

impl Component for UserLayout {
    fn render(&self) -> impl IntoElement {
        let user_id = self.user_id.clone();
        rect()
            .spacing(6.)
            .child(format!("User: {}", user_id))
            .child(
                rect()
                    .horizontal()
                    .spacing(6.)
                    .child(
                        Link::new(Route::UserDetail {
                            user_id: user_id.clone(),
                        })
                        .child(Button::new().child("Details"))
                    )
                    .child(
                        Link::new(Route::UserPosts { user_id })
                            .child(Button::new().child("Posts"))
                    )
            )
            .child(Outlet::<Route>::new())
    }
}

#[derive(PartialEq)]
struct Home;

impl Component for Home {
    fn render(&self) -> impl IntoElement {
        rect()
            .spacing(8.)
            .child("Welcome Home")
            .child(
                Button::new()
                    .on_press(|_| {
                        RouterContext::get().push(Route::UserDetail {
                            user_id: "alice".to_string(),
                        });
                    })
                    .child("Visit Alice")
            )
    }
}

#[derive(PartialEq)]
struct About;

impl Component for About {
    fn render(&self) -> impl IntoElement {
        rect().child("About this app")
    }
}

#[derive(PartialEq)]
struct UserDetail {
    user_id: String,
}

impl Component for UserDetail {
    fn render(&self) -> impl IntoElement {
        rect().child(format!("Details for {}", self.user_id))
    }
}

#[derive(PartialEq)]
struct UserPosts {
    user_id: String,
}

impl Component for UserPosts {
    fn render(&self) -> impl IntoElement {
        rect().child(format!("Posts by {}", self.user_id))
    }
}

Best Practices

  1. Type-safe routes - Let the compiler catch navigation errors
  2. Flat route structure - Avoid overly deep nesting
  3. Descriptive names - Use clear route variant names
  4. Consistent parameters - Use the same type for the same concept
  5. Layout composition - Reuse layouts across route groups

API Reference

See the API documentation for complete details.

Build docs developers (and LLMs) love