freya-router crate. Build single-page applications with multiple views, nested layouts, and programmatic navigation.
Quick Start
Define routes using an enum with theRoutable 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 },
}
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))
}
}
Navigation
Link Component
Navigate declaratively with theLink 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
UseRouterContext for imperative navigation:
use freya::router::RouterContext;
Button::new()
.on_press(|_| {
RouterContext::get().push(Route::Settings);
})
.child("Go to Settings")
Navigation Methods
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
- Type-safe routes - Let the compiler catch navigation errors
- Flat route structure - Avoid overly deep nesting
- Descriptive names - Use clear route variant names
- Consistent parameters - Use the same type for the same concept
- Layout composition - Reuse layouts across route groups