Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs-src/0.7/src/essentials/router/layouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,52 @@ readability):
<footer>footer</footer>
```

## Multiple routes sharing a layout

When you want multiple routes to share the same layout, you need to understand that **`#[layout()]` creates a stateful scope** that remains active for all subsequent routes until explicitly closed.

### Correct Pattern

Use indentation to show hierarchy and close the layout scope with `#[end_layout]`:

```rust
{{#include ../docs-router/src/doc_examples/outlet.rs:multiple_routes}}
```

Both the `Home` and `About` routes will render inside the same `Wrapper` layout. The layout scope is explicitly closed with `#[end_layout]`, making it clear which routes are affected.

### Common Mistake: Nested Layouts

**⚠️ Warning:** Adding `#[layout()]` multiple times without closing the scope creates nested layouts:

```rust
{{#include ../docs-router/src/doc_examples/outlet.rs:wrong_pattern}}
```

In this example, the `About` route will render with **two** `Wrapper` components (double header, double footer) because:
1. The first `#[layout(Wrapper)]` opens a layout scope
2. The second `#[layout(Wrapper)]` **nests inside** the first scope (instead of replacing it)
3. No `#[end_layout]` was used to close the first scope

This produces duplicate UI elements with no compile-time warning.

### Layout Scoping Rules

- `#[layout(Component)]` opens a layout scope for all subsequent routes
- The scope remains active until:
- `#[end_layout]` explicitly closes it, OR
- The end of the enum is reached
- Subsequent `#[layout()]` attributes without closing the previous scope create **nested** layouts
- Use proper indentation to visually represent the scope hierarchy

### Debugging Layout Issues

If you see duplicate layouts in your rendered UI:

1. **Check HTML output**: Use browser DevTools or `curl http://localhost:8080/route | grep 'class="your-layout-class"' | wc -l`
2. **Verify nesting levels**: Run `cargo expand --package your-crate --lib routes` to see the macro-generated code
3. **Add `#[end_layout]`**: Explicitly close layout scopes to prevent accidental nesting

## Layouts with dynamic segments

You can combine layouts with nested routes to create dynamic layouts with content that changes based on the current route.
Expand Down
104 changes: 104 additions & 0 deletions packages/docs-router/src/doc_examples/outlet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,107 @@ mod use_route {
);
}
}

mod multiple_routes {
use dioxus::prelude::*;

// ANCHOR: multiple_routes
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[layout(Wrapper)]
#[route("/")]
Home {},

#[route("/about")]
About {},
#[end_layout]
}

#[component]
fn Wrapper() -> Element {
rsx! {
header { "header" }
Outlet::<Route> {}
footer { "footer" }
}
}

#[component]
fn Home() -> Element {
rsx! { h1 { "Home" } }
}

#[component]
fn About() -> Element {
rsx! { h1 { "About" } }
}
// ANCHOR_END: multiple_routes

fn App() -> Element {
rsx! { Router::<Route> {} }
}

fn main() {
let mut vdom = VirtualDom::new(App);
vdom.rebuild_in_place();
let html = dioxus_ssr::render(&vdom);
assert_eq!(
html,
"<header>header</header><h1>Home</h1><footer>footer</footer>"
);
}
}

mod wrong_pattern {
use dioxus::prelude::*;

// ANCHOR: wrong_pattern
#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
// ❌ WRONG: Creates nested layouts
#[layout(Wrapper)]
#[route("/")]
Home {},

#[layout(Wrapper)] // This nests inside the first layout!
#[route("/about")]
About {},
}
// ANCHOR_END: wrong_pattern

#[component]
fn Wrapper() -> Element {
rsx! {
header { "header" }
Outlet::<Route> {}
footer { "footer" }
}
}

#[component]
fn Home() -> Element {
rsx! { h1 { "Home" } }
}

#[component]
fn About() -> Element {
rsx! { h1 { "About" } }
}

fn App() -> Element {
rsx! { Router::<Route> {} }
}

fn main() {
let mut vdom = VirtualDom::new(App);
vdom.rebuild_in_place();
let html = dioxus_ssr::render(&vdom);
// About page renders with DOUBLE layout (2 headers, 2 footers)
assert_eq!(
html,
"<header>header</header><header>header</header><h1>About</h1><footer>footer</footer><footer>footer</footer>"
);
}
}
Loading