finish router refrence

This commit is contained in:
Evan Almloff committed 2023-06-01 17:31:13 -05:00
1 parent 07446386e6
commit b5a2f0d6cb
61 files changed
+676 -1908

No files matched your search

@@ -1,166 +0,0 @@
# Catch All Routes
Many modern web apps store parameters within their current path. This allows
users to share URLs that link to a specific bit of content. We can create this
functionality with catch all routes.
> If you want to change what route is active based on the format of the
> parameter, see [Matching Routes](./matching.md).
> The parameter will be URL decoded.
## Creating a content component
We start by creating a component that uses the parameters value.
We can get the current state of the router using the [`use_route`] hook. From
that state we can extract the current value of our parameter by using a key we
will later also define on our route.
> It is **VERY IMPORTANT** to drop the object returned by the [`use_route`]
> hook once our component finished rendering. Otherwise the entire router will
> be frozen.
> The [`use_route`] hook can only be used in components nested within a
> component that called [`use_router`].
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
use dioxus::prelude::*;
# extern crate dioxus_router;
use dioxus_router::prelude::*;
struct Name;
fn Greeting(cx: Scope) -> Element {
let route = use_route(cx).expect("is nested within a Router component");
let name = route.parameter::<Name>()
.map(|name| name.clone())
.unwrap_or(String::from("world"));
render! {
p { "Hello, {name}!" }
}
}
```
## Defining the routes
Now we can define our route. Unlike a fixed [`Route`], a [`ParameterRoute`]
needs two arguments to be created.
> Also note that each [`Segment`] can have exactly one parameter or
> [fallback route](./fallback.md).
>
> For that reason, the example below would not work in practice, but showing
> both forms (explicit and short) is more important for this example.
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
# use dioxus::prelude::*;
# extern crate dioxus_router;
# use dioxus_router::prelude::*;
# fn Greeting(cx: Scope) -> Element { unimplemented!() }
#
struct Name;
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
..Default::default()
},
&|| {
Segment::empty()
.catch_all(ParameterRoute::content::<Name>(comp(Greeting)))
.catch_all((comp(Greeting), Name { })) // same in short
}
);
// ...
# unimplemented!()
}
```
## Interaction with other routes
Each individual [`Segment`] can only ever have one active route. This means that
when a [`Segment`] has more than just a catch all route, the router has to
decide which is active. It does that this way:
0. If the segment is not specified (i.e. `/`), then the index route will be
active.
1. If a [_fixed_](./index.md#fixed-routes) route matches the current path, it
will be active.
2. If a [_matching_ route](./matching.md) matches the current path, it will be
active. _Matching_ routes are checked in the order they are defined.
3. If neither a _fixed_ nor a _matching_ route is active, the _catch all_ route
or [_fallback_ route](./fallback.md) will be.
Step 0 means that if we want a parameter to be empty, that needs to be specified
by the path, i.e. `//`.
> Be careful with using catch all routes on the root [`Segment`]. Navigating to
> paths starting with `//` will **NOT** work. This is not a limitation of the
> router, but rather of how relative URLs work.
>
> If you absolutely need an empty parameter on the root [`Segment`], a URL like
> this _could_ work:
>
> - `https://your-site.example//` for web sites
> - `dioxus://index.html//` for desktop apps
## Full Code
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
# use dioxus::prelude::*;
# extern crate dioxus_router;
# use dioxus_router::{history::MemoryHistory, prelude::*};
# extern crate dioxus_ssr;
#
struct Name;
fn Greeting(cx: Scope) -> Element {
let route = use_route(cx).expect("is nested within a Router component");
let name = route.parameter::<Name>()
.map(|name| name.clone())
.unwrap_or(String::from("world"));
render! {
p { "Hello, {name}!" }
}
}
fn App(cx: Scope) -> Element {
let routes = use_router(
cx,
&|| RouterConfiguration {
# synchronous: true,
# history: Box::new(MemoryHistory::with_initial_path("/Dioxus").unwrap()),
..Default::default()
},
&|| Segment::empty().catch_all((comp(Greeting), Name { }))
);
// ...
render! {
Outlet { }
}
}
#
# let mut vdom = VirtualDom::new(App);
# vdom.rebuild();
# assert_eq!(
# dioxus_ssr::render(&vdom),
# "<p>Hello, Dioxus!</p>"
# );
```
[`ParameterRoute`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.ParameterRoute.html
[`Route`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Route.html
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
[`use_route`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_route.html
[`use_router`]: https://docs.rs/dioxus-router/latest/dioxus_router/hooks/fn.use_router.html
@@ -1,140 +0,0 @@
# Fallback Routes
Sometimes the router might be unable to find a route for the provided path. We
might want it to show a prepared error message to our users in that case.
Fallback routes allow us to do that.
> This is especially important for use cases where users can manually change the
> path, like web apps running in the browser.
## A single global fallback
To catch all cases of invalid paths within our app, we can simply add a fallback
route to our root [`Segment`].
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
use dioxus::prelude::*;
# extern crate dioxus_router;
use dioxus_router::{history::MemoryHistory, prelude::*};
# extern crate dioxus_ssr;
fn Index(cx: Scope) -> Element {
render! {
h1 { "Index" }
}
}
fn Fallback(cx: Scope) -> Element {
render! {
h1 { "Error 404 - Not Found" }
p { "The page you asked for doesn't exist." }
}
}
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
# synchronous: true,
# history: Box::new(MemoryHistory::with_initial_path("/invalid").unwrap()),
..Default::default()
},
&|| {
Segment::content(comp(Index)).fallback(comp(Fallback))
}
);
render! {
Outlet { }
}
}
#
# let mut vdom = VirtualDom::new(App);
# vdom.rebuild();
# assert_eq!(
# dioxus_ssr::render(&vdom),
# "<h1>Error 404 - Not Found</h1><p>The page you asked for doesn't exist.</p>"
# );
```
## More specific fallback routes
In some cases we might want to show different fallback content depending on what
section of our app the user is in.
For example, our app might have several settings pages under `/settings`, such
as the password settings `/settings/password` or the privacy settings
`/settings/privacy`. When our user is in the settings section, we want to show
them _"settings not found"_ instead of _"page not found"_.
We can easily do that by setting a fallback route on our nested [`Segment`]. It
will then replace the global fallback whenever our [`Segment`] was active.
Note the `.clear_fallback(false)` part. If we didn't add this, the fallback
content would be rendered inside the `Settings` component.
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
use dioxus::prelude::*;
# extern crate dioxus_router;
use dioxus_router::{history::MemoryHistory, prelude::*};
# extern crate dioxus_ssr;
// This example doesn't show the index or settings components. It only shows how
// to set up several fallback routes.
# fn Index(cx: Scope) -> Element { unimplemented!() }
# fn Settings(cx: Scope) -> Element { unimplemented!() }
# fn GeneralSettings(cx: Scope) -> Element { unimplemented!() }
# fn PasswordSettings(cx: Scope) -> Element { unimplemented!() }
# fn PrivacySettings(cx: Scope) -> Element { unimplemented!() }
fn GlobalFallback(cx: Scope) -> Element {
render! {
h1 { "Error 404 - Page Not Found" }
}
}
fn SettingsFallback(cx: Scope) -> Element {
render! {
h1 { "Error 404 - Settings Not Found" }
}
}
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
# synchronous: true,
# history: Box::new(MemoryHistory::with_initial_path("/settings/invalid").unwrap()),
..Default::default()
},
&|| {
Segment::empty()
.fixed("settings", Route::content(comp(Settings)).nested(
Segment::content(comp(GeneralSettings))
.fixed("password", comp(PasswordSettings))
.fixed("privacy", comp(PrivacySettings))
.fallback(comp(SettingsFallback))
.clear_fallback(true)
))
.fallback(comp(GlobalFallback))
}
);
render! {
Outlet { }
}
}
#
# let mut vdom = VirtualDom::new(App);
# vdom.rebuild();
# assert_eq!(
# dioxus_ssr::render(&vdom),
# "<h1>Error 404 - Settings Not Found</h1>"
# );
```
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
+41 -119
View File
@@ -1,143 +1,65 @@
# Defining Routes
When creating a router we need to pass it a [`Segment`]. It tells the router
about all the routes of our app.
When creating a [`Routable`] enum, we can define routes for our application using the `route("path")` attribute.
## Example content
## Route Segments
To get a good understanding of how we define routes we first need to prepare
some example content, so we can see the routing in action.
Each route is made up of segments. Most segments are separated by `/` characters in the path.
There are four fundamental types of segments:
1. [Static segments](#static-segments) are fixed strings that must be present in the path.
2. [Dynamic segments](#dynamic-segments) are types that can be parsed from a segment.
3. [Catch-all segments](#catch-all-segments) are types that can be parsed from multiple segments.
4. [Query segments](#query-segments) are types that can be parsed from the query string.
Routes are matched:
- First, from most specific to least specific (Static then Dynamic then Catch All) (Query is 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`.
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
use dioxus::prelude::*;
# extern crate dioxus_router;
use dioxus_router::prelude::*;
fn Index(cx: Scope) -> Element {
render! {
h1 { "Welcome to our test site!" }
}
}
fn Other(cx: Scope) -> Element {
render! {
p { "some other content" }
}
}
{{#include ../../../examples/static_segments.rs:route}}
```
## Index routes
## Dynamic Segments
The easiest thing to do is to define an index route.
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.
Index routes act very similar to `index.html` files in most web servers. They
are active, when we don't specify a route.
> Note that we wrap our `Index` component with [`comp`]. This is because of
> rust type system requirements.
The segment can be of any type that implements `FromStr`.
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
# use dioxus::prelude::*;
# extern crate dioxus_router;
# use dioxus_router::prelude::*;
# fn Index(cx: Scope) -> Element { unimplemented!() }
#
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
..Default::default()
},
&|| Segment::content(comp(Index))
);
// ...
# unimplemented!()
}
{{#include ../../../examples/dynamic_segments.rs:route}}
```
## Fixed routes
## Catch All Segments
It is almost as easy to define a fixed route.
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.
Fixed routes work similar to how web servers treat files. They are active, when
specified in the path. In the example, the path must be `/other`.
The segment can be of any type that implements `FromSegments`. (Vec<String> implements this by default)
> The path will be URL decoded before checking if it matches our route.
Catch All segments must be the _last route segment_ in the path (query segments are not counted) and cannot be included in nests.
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
# use dioxus::prelude::*;
# extern crate dioxus_router;
# use dioxus_router::prelude::*;
# fn Index(cx: Scope) -> Element { unimplemented!() }
# fn Other(cx: Scope) -> Element { unimplemented!() }
#
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
..Default::default()
},
&|| Segment::content(comp(Index)).fixed("other", comp(Other))
// ^ note the absence of a / prefix
);
// ...
# unimplemented!()
}
{{#include ../../../examples/catch_all_segments.rs:route}}
```
## Full Code
## Query Segments
Query segments are in the form of `?:name` where `name` is the name of the field in the route variant.
Unlike [Dynamic Segments](#dynamic-segments) and [Catch All Segments](#catch-all-segments), parsing a Query segment must not fail.
The segment can be of any type that implements `FromQuery`.
Query segments must be the _after all route segments_ and cannot be included in nests.
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
use dioxus::prelude::*;
# extern crate dioxus_router;
use dioxus_router::{history::MemoryHistory, prelude::*};
# extern crate dioxus_ssr;
fn Index(cx: Scope) -> Element {
render! {
h1 { "Welcome to our test site!" }
}
}
fn Other(cx: Scope) -> Element {
render! {
p { "some other content" }
}
}
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
# synchronous: true,
# history: Box::new(MemoryHistory::with_initial_path("/other").unwrap()),
..Default::default()
},
&|| Segment::content(comp(Index)).fixed("other", comp(Other))
);
render! {
Outlet { }
}
}
#
# let mut vdom = VirtualDom::new(App);
# vdom.rebuild();
# assert_eq!(
# dioxus_ssr::render(&vdom),
# "<p>some other content</p>"
# );
{{#include ../../../examples/query_segments.rs:route}}
```
[`comp`]: https://docs.rs/dioxus-router/latest/dioxus_router/prelude/fn.comp.html
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html
@@ -1,139 +0,0 @@
# Matching Routes
> Make sure you understand how [catch all routes](./catch_all.md) work before
> reading this page.
When accepting parameters via the path, some complex applications might need to
decide what route should be active based on the format of that parameter.
_Matching_ routes make it easy to implement such behavior.
> The parameter will be URL decoded, both for checking if the route is active
> and when it is provided to the application.
> The example below is only for showing _matching route_ functionality. It is
> unfit for all other purposes.
## Code Example
> Notice that the parameter of a _matching route_ has the same type as a
> [_catch all route_](./catch_all.md).
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
# use dioxus::prelude::*;
# extern crate dioxus_router;
# use dioxus_router::{history::MemoryHistory, prelude::*};
# extern crate dioxus_ssr;
# extern crate regex;
use regex::Regex;
struct Name;
fn GreetingFemale(cx: Scope) -> Element {
let route = use_route(cx).unwrap();
let name = route.parameter::<Name>()
.map(|name| {
let mut name = name.to_string();
name.remove(0);
name
})
.unwrap_or(String::from("Anonymous"));
render! {
p { "Hello Mrs. {name}" }
}
}
fn GreetingMale(cx: Scope) -> Element {
let route = use_route(cx).unwrap();
let name = route.parameter::<Name>()
.map(|name| {
let mut name = name.to_string();
name.remove(0);
name
})
.unwrap_or(String::from("Anonymous"));
render! {
p { "Hello Mr. {name}" }
}
}
fn GreetingWithoutGender(cx: Scope) -> Element {
let route = use_route(cx).unwrap();
let name = route.parameter::<Name>()
.map(|name| name.to_string())
.unwrap_or(String::from("Anonymous"));
render! {
p { "Hello {name}" }
}
}
fn GreetingKenobi(cx: Scope) -> Element {
render! {
p { "Hello there." }
p { "General Kenobi." }
}
}
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
# synchronous: true,
# history: Box::new(MemoryHistory::with_initial_path("/fAnna").unwrap()),
..Default::default()
},
&|| {
Segment::empty()
.fixed("kenobi", comp(GreetingKenobi))
.matching(
Regex::new("^f").unwrap(),
ParameterRoute::content::<Name>(comp(GreetingFemale))
)
.matching(
Regex::new("^m").unwrap(),
(comp(GreetingMale), Name { })
)
.catch_all((comp(GreetingWithoutGender), Name { }))
}
);
render! {
Outlet { }
}
}
#
# let mut vdom = VirtualDom::new(App);
# vdom.rebuild();
# let html = dioxus_ssr::render(&vdom);
# assert_eq!(html, "<p>Hello Mrs. Anna</p>");
```
## Matcher
In the example above, both _matching routes_ use a regular expression to specify
when they match. However, _matching routes_ are not limited to those. They
accept all types that implement the [`Matcher`] trait.
For example, you could (but probably shouldn't) implement a matcher, that
matches all values with an even number of characters:
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus_router;
# use dioxus_router::prelude::*;
#
#[derive(Debug)]
struct EvenMatcher;
impl Matcher for EvenMatcher {
fn matches(&self, value: &str) -> bool {
value.len() % 2 == 0
}
}
```
[`Matcher`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/trait.Matcher.html
@@ -1,63 +0,0 @@
# Multiple Components & Redirects
## Multiple Components
When creating complex apps we sometimes want to have multiple pieces of content
side by side. The router allows us to do this. For more details see the section
about [named `Outlet`s](../outlets.md#named-outlets).
## Redirects
In some cases we may want to redirect our users to another page whenever they
open a specific path. We can tell the router to do this when defining our
routes.
> Redirects to external pages only work in certain conditions. For more details
> see the chapter about [external navigation failures](../failures/external.md).
In the following example we will redirect everybody from `/` and `/start` to
`/home`.
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
use dioxus::prelude::*;
# extern crate dioxus_router;
use dioxus_router::{history::MemoryHistory, prelude::*};
# extern crate dioxus_ssr;
fn Home(cx: Scope) -> Element {
render! {
h1 { "Home Page" }
}
}
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
# synchronous: true,
# history: Box::new(MemoryHistory::with_initial_path("/home").unwrap()),
..Default::default()
},
&|| {
Segment::content(comp(Home))
// notice that we use RouteContent::Redirect instead of
// RouteContent::Content (which we have been using indirectly)
.fixed(
"home",
RouteContent::Redirect(NavigationTarget::Internal("/".into()))
)
.fixed("start", "/") // short form
});
render! {
Outlet { }
}
}
#
# let mut vdom = VirtualDom::new(App);
# vdom.rebuild();
# let html = dioxus_ssr::render(&vdom);
# assert_eq!(html, "<h1>Home Page</h1>");
```
+9 -187
View File
@@ -19,199 +19,21 @@ We might want to map this structure to these paths and components:
/settings/privacy -> Settings { PrivacySettings }
```
Nested routes allow us to do this.
Nested routes allow us to do this without repeating /settings in every route.
## Route Depth
## Nesting
With nesting routes, the router manages content on multiple levels. In our
example, when the path is `/settings`, there are two levels of content:
To nest routes, we use the `#[nest("path")]` and `#[end_nest]` attributes.
0. The `Settings` component
1. The `GeneralSettings` component
The path in nest must not:
Dioxus Router uses the [`Outlet`] component to actually render content, but each
[`Outlet`] can only render content from one level. This means that for the
content of nested routes to actually be rendered, we also need nested
[`Outlet`]s.
1. Contain a [Catch All Segment](index.md#catch-all-segments)
2. Contain a [Query Segment](index.md#query-segments)
## Defining the content components
If you define a dynamic segment in a nest, it will be available to all child routes and layouts.
We start by creating the components we want the router to render.
Take a look at the `Settings` component. When it gets rendered by an [`Outlet`],
it will render a second [`Outlet`]. Thus the second [`Outlet`] is nested within
the first one, and will in turn render our nested content.
To finish a nest, we use the `#[end_nest]` attribute or the end of the enum.
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
# use dioxus::prelude::*;
# extern crate dioxus_router;
# use dioxus_router::prelude::*;
#
fn Settings(cx: Scope) -> Element {
render! {
h1 { "Settings" }
Outlet { }
}
}
fn GeneralSettings(cx: Scope) -> Element {
render! {
h2 { "General Settings" }
}
}
fn PWSettings(cx: Scope) -> Element {
render! {
h2 { "Password Settings" }
}
}
fn PrivacySettings(cx: Scope) -> Element {
render! {
h2 { "Privacy Settings" }
}
}
{{#include ../../../examples/nest.rs:route}}
```
## Defining the root [`Segment`]
Now we create the [`Segment`] that we will pass to the router.
Note that we wrap `comp(Settings)` within a [`Route`]. For this exact code that
is unnecessary, as this would be done automatically. However, in the next step
we'll use a method of [`Route`], so we might as well add this now.
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
# use dioxus::prelude::*;
# extern crate dioxus_router;
# use dioxus_router::prelude::*;
# fn Settings(cx: Scope) -> Element { unimplemented!() }
#
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
..Default::default()
},
&|| Segment::empty().fixed("settings", Route::content(comp(Settings)))
);
// ...
# unimplemented!()
}
```
## Defining the nested [`Segment`]
In order to create nested routes we need to create a nested [`Segment`]. We then
pass it to the [`Route`] on the root segment.
> A [`Segment`] always refers to one exact segment of the path.
>
> https://router.example/`root_segment`/`first_nested_segment`/`second_nested_segment`/...
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
# use dioxus::prelude::*;
# extern crate dioxus_router;
# use dioxus_router::prelude::*;
# fn Settings(cx: Scope) -> Element { unimplemented!() }
# fn GeneralSettings(cx: Scope) -> Element { unimplemented!() }
# fn PWSettings(cx: Scope) -> Element { unimplemented!() }
# fn PrivacySettings(cx: Scope) -> Element { unimplemented!() }
#
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
..Default::default()
},
&|| Segment::empty().fixed(
"settings",
Route::content(comp(Settings)).nested(
Segment::content(comp(GeneralSettings))
.fixed("password", comp(PWSettings))
.fixed("privacy", comp(PrivacySettings))
)
)
);
// ...
# unimplemented!()
}
```
## Full Code
```rust, no_run
# // Hidden lines (like this one) make the documentation tests work.
# extern crate dioxus;
# use dioxus::prelude::*;
# extern crate dioxus_router;
# use dioxus_router::{history::MemoryHistory, prelude::*};
# extern crate dioxus_ssr;
#
fn Settings(cx: Scope) -> Element {
render! {
h1 { "Settings" }
Outlet { }
}
}
fn GeneralSettings(cx: Scope) -> Element {
render! {
h2 { "General Settings" }
}
}
fn PWSettings(cx: Scope) -> Element {
render! {
h2 { "Password Settings" }
}
}
fn PrivacySettings(cx: Scope) -> Element {
render! {
h2 { "Privacy Settings" }
}
}
fn App(cx: Scope) -> Element {
use_router(
cx,
&|| RouterConfiguration {
# synchronous: true,
# history: Box::new(MemoryHistory::with_initial_path("/settings/privacy").unwrap()),
..Default::default()
},
&|| Segment::empty().fixed(
"settings",
Route::content(comp(Settings)).nested(
Segment::content(comp(GeneralSettings))
.fixed("password", comp(PWSettings))
.fixed("privacy", comp(PrivacySettings))
)
)
);
render! {
Outlet { }
}
}
#
# let mut vdom = VirtualDom::new(App);
# vdom.rebuild();
# assert_eq!(
# dioxus_ssr::render(&vdom),
# "<h1>Settings</h1><h2>Privacy Settings</h2>"
# );
```
[`Outlet`]: https://docs.rs/dioxus-router/latest/dioxus_router/components/fn.Outlet.html
[`Route`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Route.html
[`Segment`]: https://docs.rs/dioxus-router-core/latest/dioxus_router_core/routes/struct.Segment.html