Nested routes allow you to create layouts that wrap multiple pages, building complex UI hierarchies with shared components.
Layouts
A layout is a component that wraps other routes. It renders the Outlet component, which is replaced by the current child route.
Basic Layout
use dioxus::prelude::*;
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
#[layout(NavBar)] // Wrap routes in NavBar layout
#[route("/")]
Home {},
#[route("/about")]
About {},
#[end_layout]
}
#[component]
fn NavBar() -> Element {
rsx! {
nav {
Link { to: Route::Home {}, "Home" }
Link { to: Route::About {}, "About" }
}
// Child routes render here
Outlet::<Route> {}
}
}
#[component]
fn Home() -> Element {
rsx! { h1 { "Home Page" } }
}
#[component]
fn About() -> Element {
rsx! { h1 { "About Page" } }
}
When you navigate to /, the router renders:
NavBar component with navigation
Home component where Outlet is placed
The Outlet Component
The Outlet component is a placeholder that renders the current child route:
#[component]
fn Layout() -> Element {
rsx! {
header { "App Header" }
main {
Outlet::<Route> {} // Child routes render here
}
footer { "App Footer" }
}
}
Every layout component must include exactly one Outlet component, or child routes won’t render.
Nesting Routes
Use #[nest()] to create URL hierarchies:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
#[nest("/blog")] // All nested routes start with /blog
#[route("/")] // Matches /blog/
BlogList {},
#[route("/:id")] // Matches /blog/:id
BlogPost { id: String },
#[end_nest]
}
This creates these URLs:
/blog/ → BlogList
/blog/hello → BlogPost { id: "hello" }
Nest with Layout
Combine nesting and layouts:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
#[nest("/blog")]
#[layout(BlogLayout)] // Layout for all blog routes
#[route("/")]
BlogList {},
#[route("/:id")]
BlogPost { id: String },
#[end_layout]
#[end_nest]
}
#[component]
fn BlogLayout() -> Element {
rsx! {
header { h1 { "My Blog" } }
nav {
Link { to: Route::BlogList {}, "All Posts" }
}
main { Outlet::<Route> {} }
footer { "© 2024" }
}
}
Both BlogList and BlogPost will render inside the BlogLayout component.
Multiple Nested Levels
Create deeply nested route structures:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
// Root layout
#[layout(RootLayout)]
#[route("/")]
Home {},
// Nested section with its own layout
#[nest("/admin")]
#[layout(AdminLayout)]
#[route("/")]
AdminDashboard {},
// Further nested routes
#[nest("/users")]
#[route("/")]
UserList {},
#[route("/:id")]
UserDetail { id: usize },
#[end_nest]
#[end_layout]
#[end_nest]
#[end_layout]
}
This creates URLs:
/ → RootLayout → Home
/admin/ → RootLayout → AdminLayout → AdminDashboard
/admin/users/ → RootLayout → AdminLayout → UserList
/admin/users/123 → RootLayout → AdminLayout → UserDetail
Nested Layout Example
#[component]
fn RootLayout() -> Element {
rsx! {
header { "Site Header" }
Outlet::<Route> {} // AdminLayout or Home renders here
footer { "Site Footer" }
}
}
#[component]
fn AdminLayout() -> Element {
rsx! {
div { class: "admin-container",
aside {
Link { to: Route::AdminDashboard {}, "Dashboard" }
Link { to: Route::UserList {}, "Users" }
}
main { Outlet::<Route> {} } // Admin pages render here
}
}
}
Outlet Context
Share data between layouts and child routes using outlet context:
#[component]
fn BlogLayout() -> Element {
// Provide context to child routes
use_context_provider(|| Signal::new(BlogContext {
author: "John Doe".to_string(),
}));
rsx! {
header { "Blog" }
Outlet::<Route> {}
}
}
#[component]
fn BlogPost(id: String) -> Element {
// Access context from parent layout
let context = use_context::<Signal<BlogContext>>();
rsx! {
h1 { "Post {id}" }
p { "By {context.read().author}" }
}
}
Layout Patterns
App Shell Pattern
A common layout with header, sidebar, and main content:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
#[layout(AppShell)]
#[route("/")]
Dashboard {},
#[route("/profile")]
Profile {},
#[route("/settings")]
Settings {},
#[end_layout]
}
#[component]
fn AppShell() -> Element {
rsx! {
div { class: "app-shell",
header { class: "header",
h1 { "My App" }
}
div { class: "container",
aside { class: "sidebar",
nav {
Link { to: Route::Dashboard {}, "Dashboard" }
Link { to: Route::Profile {}, "Profile" }
Link { to: Route::Settings {}, "Settings" }
}
}
main { class: "main-content",
Outlet::<Route> {}
}
}
}
}
}
Marketing Site Pattern
Different layouts for marketing and app sections:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
// Marketing pages with minimal layout
#[layout(MarketingLayout)]
#[route("/")]
Landing {},
#[route("/pricing")]
Pricing {},
#[end_layout]
// App pages with full navigation
#[nest("/app")]
#[layout(AppLayout)]
#[route("/")]
Dashboard {},
#[route("/projects")]
Projects {},
#[end_layout]
#[end_nest]
}
#[component]
fn MarketingLayout() -> Element {
rsx! {
header { class: "marketing-header",
Link { to: Route::Landing {}, "Home" }
Link { to: Route::Pricing {}, "Pricing" }
Link { to: Route::Dashboard {}, "Login" }
}
Outlet::<Route> {}
}
}
#[component]
fn AppLayout() -> Element {
rsx! {
div { class: "app",
nav { class: "app-nav",
Link { to: Route::Dashboard {}, "Dashboard" }
Link { to: Route::Projects {}, "Projects" }
}
Outlet::<Route> {}
}
}
}
Tabbed Interface
Use nested routes for tabs:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
#[nest("/settings")]
#[layout(SettingsTabs)]
#[route("/")]
#[redirect("/", || Route::GeneralSettings {})]
#[route("/general")]
GeneralSettings {},
#[route("/privacy")]
PrivacySettings {},
#[route("/notifications")]
NotificationSettings {},
#[end_layout]
#[end_nest]
}
#[component]
fn SettingsTabs() -> Element {
rsx! {
h1 { "Settings" }
nav { class: "tabs",
Link {
to: Route::GeneralSettings {},
active_class: "active",
"General"
}
Link {
to: Route::PrivacySettings {},
active_class: "active",
"Privacy"
}
Link {
to: Route::NotificationSettings {},
active_class: "active",
"Notifications"
}
}
Outlet::<Route> {}
}
}
Without Nesting URL
You can use layouts without nesting the URL path:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
#[layout(Layout)]
#[route("/home")] // URL is /home, not /layout/home
Home {},
#[route("/about")] // URL is /about, not /layout/about
About {},
#[end_layout]
}
Multiple Layouts at Same Level
You can’t have multiple active layouts at the same nesting level. Instead, use separate route groups:
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
// First layout group
#[layout(PublicLayout)]
#[route("/")]
Home {},
#[route("/about")]
About {},
#[end_layout]
// Second layout group
#[layout(AuthLayout)]
#[route("/login")]
Login {},
#[route("/signup")]
Signup {},
#[end_layout]
}
Best Practices
- Use layouts for shared UI - Headers, navigation, footers
- Keep layouts simple - Layouts should focus on structure, not business logic
- Use outlet context sparingly - Prefer explicit props when possible
- Group related routes - Use nesting to organize routes logically
- Add rustfmt::skip - Prevents reformatting of route structure
Common Mistakes
Forgetting Outlet
// ❌ Wrong - no Outlet
#[component]
fn Layout() -> Element {
rsx! {
nav { "Navigation" }
// Child routes won't render!
}
}
// ✅ Correct
#[component]
fn Layout() -> Element {
rsx! {
nav { "Navigation" }
Outlet::<Route> {}
}
}
// ❌ Wrong - missing #[end_layout]
#[derive(Routable, Clone, PartialEq)]
enum Route {
#[layout(Layout)]
#[route("/")]
Home {},
// Missing #[end_layout]!
}
// ✅ Correct
#[derive(Routable, Clone, PartialEq)]
#[rustfmt::skip]
enum Route {
#[layout(Layout)]
#[route("/")]
Home {},
#[end_layout]
}
Wrong Nesting Order
// ❌ Wrong - layout after nest
#[nest("/blog")]
#[layout(BlogLayout)]
// ✅ Correct - nest then layout
#[nest("/blog")]
#[layout(BlogLayout)]