Defining Routes
When creating a Routable enum, we can define routes for our application using the route("path") attribute.
Route Segments
Each route is made up of segments. Most segments are separated by / characters in the path.
There are five fundamental types of segments:
- Static segments are fixed strings that must be present in the path.
- Dynamic segments are types that can be parsed from a segment.
- Catch-all segments are types that can be parsed from multiple segments.
- Query segments are types that can be parsed from the query string.
- Hash fragments are types that can be parsed from the hash fragment.
Routes are matched:
- First, from most specific to least specific (Static then Dynamic then Catch All) (Query and hash are always matched)
- Then, if multiple routes match the same path, the order in which they are defined in the enum is followed.
Static segments
Fixed routes match a specific path. For example, the route #[route("/about")] will match the path /about.
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// Routes always start with a slash
#[route("/")]
Home {},
// You can have multiple segments in a route
#[route("/hello/world")]
HelloWorld {},
}
#[component]
fn Home() -> Element {
todo!()
}
#[component]
fn HelloWorld() -> Element {
todo!()
}Dynamic Segments
Dynamic segments are in the form of :name where name is the name of the field in the route variant. If the segment is parsed successfully then the route matches, otherwise the matching continues.
The segment can be of any type that implements FromStr.
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// segments that start with : are dynamic segments
#[route("/post/:name")]
BlogPost {
// You must include dynamic segments in child variants
name: String,
},
#[route("/document/:id")]
Document {
// You can use any type that implements FromStr
// If the segment can't be parsed, the route will not match
id: usize,
},
}
// Components must contain the same dynamic segments as their corresponding variant
#[component]
fn BlogPost(name: String) -> Element {
todo!()
}
#[component]
fn Document(id: usize) -> Element {
todo!()
}Parsing your own dynamic segment types
Any type that implements FromStr + Display can be used as a dynamic segment. If parsing fails, the route won't match and the router moves on to the next candidate. This lets you restrict which URLs match a route — for example, only accepting known locales:
/// A locale like "en", "fr", or "es" parsed from a URL segment.
#[derive(Clone, PartialEq, Debug)]
struct Locale {
language: String,
}
/// Display is required so the router can serialize the type back into a URL.
impl fmt::Display for Locale {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.language)
}
}
/// Any type that implements FromStr can be used as a dynamic segment.
/// If parsing fails, the route won't match and the router moves on.
impl FromStr for Locale {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"en" | "fr" | "es" | "de" | "ja" => Ok(Locale {
language: s.to_string(),
}),
other => Err(format!("Unknown locale: {other}")),
}
}
}
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// With this route, /en/about and /fr/about will match,
// but /xyz/about will not.
#[route("/:locale/about")]
About { locale: Locale },
}
#[component]
fn About(locale: Locale) -> Element {
rsx! { "Viewing the about page in {locale}" }
}With this route, /en/about and /fr/about will match, but /xyz/about will not.
See FromRouteSegment on docs.rs for the full trait definition.
Catch All Segments
Catch All segments are in the form of :..name where name is the name of the field in the route variant. If the segments are parsed successfully then the route matches, otherwise the matching continues.
The segment can be of any type that implements FromSegments. ( Vec<String> implements this by default)
Catch All segments must be the last route segment in the path (query segments are not counted) and cannot be included in nests.
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// segments that start with :.. are catch all segments
#[route("/blog/:..segments")]
BlogPost {
// You must include catch all segment in child variants
segments: Vec<String>,
},
}
// Components must contain the same catch all segments as their corresponding variant
#[component]
fn BlogPost(segments: Vec<String>) -> Element {
todo!()
}Parsing your own catch-all segment types
By default, Vec<String> collects catch-all segments. You can implement FromRouteSegments and ToRouteSegments directly to parse the segments into a structured type and serialize them back into a URL:
/// A path like /docs/en/guide/intro parsed into structured data.
#[derive(Clone, PartialEq, Debug)]
struct DocPath {
locale: String,
sections: Vec<String>,
}
impl fmt::Display for DocPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.locale)?;
for section in &self.sections {
write!(f, "/{section}")?;
}
Ok(())
}
}
/// For custom catch-all types, implement both FromRouteSegments (for parsing
/// URLs into your type) and ToRouteSegments (for serializing back to a URL).
impl dioxus::router::routable::FromRouteSegments for DocPath {
type Err = String;
fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
let mut iter = segments.iter();
let locale = iter
.next()
.ok_or("Missing locale segment")?
.to_string();
let sections = iter.map(|s| s.to_string()).collect();
Ok(DocPath { locale, sections })
}
}
impl dioxus::router::routable::ToRouteSegments for DocPath {
fn display_route_segments(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
write!(f, "/{}", self.locale)?;
for section in &self.sections {
write!(f, "/{section}")?;
}
Ok(())
}
}
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[route("/docs/:..path")]
Docs { path: DocPath },
}
#[component]
fn Docs(path: DocPath) -> Element {
rsx! {
div { "Locale: {path.locale}" }
div { "Sections: {path.sections:?}" }
}
}Query Segments
Query segments are in the form of ?:name&:othername where name and othername are the names of fields in the route variant.
Unlike Dynamic Segments and Catch All Segments, parsing a Query segment must not fail.
The segment can be of any type that implements FromQueryArgument.
Query segments must be the after all route segments and cannot be included in nests.
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// segments that start with ?: are query segments
#[route("/blog?:name&:surname")]
BlogPost {
// You must include query segments in child variants
name: String,
surname: String,
},
}
#[component]
fn BlogPost(name: String, surname: String) -> Element {
rsx! {
div { "This is your blogpost with a query segment:" }
div { "Name: {name}" }
div { "Surname: {surname}" }
}
}Parsing your own query parameter types
Individual query parameters use the FromQueryArgument trait, which is auto-implemented for any FromStr + Default type. If the parameter is missing or fails to parse, Default::default() is used instead of failing the route.
You can use your own types as query parameters by implementing FromStr, Default, and Display:
/// A sort order parsed from a query parameter like ?sort=asc or ?sort=desc.
#[derive(Clone, Default, PartialEq, Debug)]
enum SortOrder {
#[default]
Asc,
Desc,
}
impl fmt::Display for SortOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SortOrder::Asc => write!(f, "asc"),
SortOrder::Desc => write!(f, "desc"),
}
}
}
/// Any type that implements FromStr + Default can be used as a query parameter.
/// If the parameter is missing or fails to parse, Default::default() is used.
impl FromStr for SortOrder {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"asc" => Ok(SortOrder::Asc),
"desc" => Ok(SortOrder::Desc),
other => Err(format!("Unknown sort order: {other}")),
}
}
}
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[route("/search?:query&:sort")]
Search {
query: String,
sort: SortOrder,
},
}
#[component]
fn Search(query: String, sort: SortOrder) -> Element {
rsx! {
div { "Searching for: {query}" }
div { "Sort order: {sort}" }
}
}If you need full control over the entire query string — for example, to handle dynamic keys or custom serialization — you can capture it into one type using the spread syntax ?:..field. The type must implement From<&str> and Display:
/// A custom type that parses the entire query string at once.
/// This is useful when you need full control over query parameter handling.
#[derive(Clone, Default, PartialEq, Debug)]
struct SearchParams {
query: String,
page: usize,
sort: String,
}
impl fmt::Display for SearchParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"query={}&page={}&sort={}",
self.query, self.page, self.sort
)
}
}
/// Implementing From<&str> gives you FromQuery automatically.
impl From<&str> for SearchParams {
fn from(query: &str) -> Self {
let mut params = SearchParams::default();
for pair in query.split('&') {
if let Some((key, value)) = pair.split_once('=') {
match key {
"query" => params.query = value.to_string(),
"page" => params.page = value.parse().unwrap_or(0),
"sort" => params.sort = value.to_string(),
_ => {}
}
}
}
params
}
}
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// Use ?:..field to capture the entire query string into a single type.
#[route("/search?:..params")]
Search { params: SearchParams },
}
#[component]
fn Search(params: SearchParams) -> Element {
rsx! {
div { "Query: {params.query}" }
div { "Page: {params.page}" }
div { "Sort: {params.sort}" }
}
}Hash Segments
Hash segments are in the form of #:field where field is a field in the route variant.
Just like Query Segments, parsing a Hash segment must not fail.
The segment can be of any type that implements FromHashFragment.
Hash fragments must be the after all route segments and any query segments and cannot be included in nests.
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// segments that start with #: are hash segments
#[route("/blog#:name")]
BlogPost {
// You must include hash segments in child variants
name: String,
},
}
#[component]
fn BlogPost(name: String) -> Element {
rsx! {
div { "This is your blogpost with a query segment:" }
div { "Name: {name}" }
}
}Parsing your own hash fragment types
The FromHashFragment trait is auto-implemented for any FromStr + Default type. Parsing failures return Default::default() instead of causing the route to fail.
You can use a custom type to parse structured data from the hash fragment:
/// A section anchor parsed from a hash fragment like #section-intro.
#[derive(Clone, Default, PartialEq, Debug)]
struct SectionAnchor {
section: String,
subsection: String,
}
impl std::fmt::Display for SectionAnchor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}", self.section, self.subsection)
}
}
/// Any type that implements FromStr + Default gets FromHashFragment
/// automatically. Parsing failures return Default::default().
impl FromStr for SectionAnchor {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_once('-') {
Some((section, sub)) => Ok(SectionAnchor {
section: section.to_string(),
subsection: sub.to_string(),
}),
None => Ok(SectionAnchor {
section: s.to_string(),
subsection: String::new(),
}),
}
}
}
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[route("/page#:anchor")]
Page { anchor: SectionAnchor },
}
#[component]
fn Page(anchor: SectionAnchor) -> Element {
rsx! {
div { "Section: {anchor.section}" }
div { "Subsection: {anchor.subsection}" }
}
}Nested Routes
When developing bigger applications we often want to nest routes within each other. As an example, we might want to organize a settings menu using this pattern:
└ Settings
├ General Settings (displayed when opening the settings)
├ Change Password
└ Privacy SettingsWe might want to map this structure to these paths and components:
/settings -> Settings { GeneralSettings }
/settings/password -> Settings { PWSettings }
/settings/privacy -> Settings { PrivacySettings }Nested routes allow us to do this without repeating /settings in every route.
Nesting
To nest routes, we use the #[nest("path")] and #[end_nest] attributes.
The path in nest must not:
- Contain a Catch All Segment
- Contain a Query Segment
If you define a dynamic segment in a nest, it will be available to all child routes and layouts.
To finish a nest, we use the #[end_nest] attribute or the end of the enum.
#[derive(Routable, Clone)]
// Skipping formatting allows you to indent nests
#[rustfmt::skip]
enum Route {
// Start the /blog nest
#[nest("/blog")]
// You can nest as many times as you want
#[nest("/:id")]
#[route("/post")]
PostId {
// You must include parent dynamic segments in child variants
id: usize,
},
// End nests manually with #[end_nest]
#[end_nest]
#[route("/:id")]
// The absolute route of BlogPost is /blog/:name
BlogPost {
id: usize,
},
// Or nests are ended automatically at the end of the enum
}
#[component]
fn BlogPost(id: usize) -> Element {
todo!()
}
#[component]
fn PostId(id: usize) -> Element {
todo!()
}