diff --git a/docs-src/0.7/src/essentials/router/layouts.md b/docs-src/0.7/src/essentials/router/layouts.md index 0d8e1480f..02bc5676b 100644 --- a/docs-src/0.7/src/essentials/router/layouts.md +++ b/docs-src/0.7/src/essentials/router/layouts.md @@ -20,6 +20,52 @@ readability): ``` +## 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. diff --git a/packages/docs-router/src/doc_examples/outlet.rs b/packages/docs-router/src/doc_examples/outlet.rs index 3aca40224..5e7cb7cd0 100644 --- a/packages/docs-router/src/doc_examples/outlet.rs +++ b/packages/docs-router/src/doc_examples/outlet.rs @@ -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:: {} + 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:: {} } + } + + fn main() { + let mut vdom = VirtualDom::new(App); + vdom.rebuild_in_place(); + let html = dioxus_ssr::render(&vdom); + assert_eq!( + html, + "
header

Home

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:: {} + footer { "footer" } + } + } + + #[component] + fn Home() -> Element { + rsx! { h1 { "Home" } } + } + + #[component] + fn About() -> Element { + rsx! { h1 { "About" } } + } + + fn App() -> Element { + rsx! { Router:: {} } + } + + 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

About

footer
" + ); + } +}