diff --git a/docs/api/route-redirect.mdx b/docs/api/route-redirect.mdx index f550440348..3fdbef3a9e 100644 --- a/docs/api/route-redirect.mdx +++ b/docs/api/route-redirect.mdx @@ -32,7 +32,9 @@ The route redirect has two configurable properties: - `from` - `to` -It redirects "from" a URL "to" another URL. When the defined `ion-route-redirect` rule matches, the router will redirect from the path specified in the `from` property to the path in the `to` property. In order for a redirect to occur the `from` path needs to be an exact match to the navigated URL. +It redirects "from" a URL "to" another URL. When the defined `ion-route-redirect` rule matches, the router will redirect from the path specified in the `from` property to the path in the `to` property. In order for a redirect to occur the `from` path needs to be an exact match to the navigated URL, unless `from` contains a `*`. A `*` matches the rest of the path from that segment on, as long as the path has at least as many segments as `from`, so `/admin/*` matches `/admin/posts` but not `/admin`. + +To render a "not found" page for unknown paths instead of redirecting, use a [catch-all route](route.mdx#catch-all-routes). ## Multiple Route Redirects {/* #multiple-route-redirects */} @@ -58,7 +60,7 @@ If the user navigates to `/admin` the router will redirect to `/login` and stop will NOT redirect if the user navigates to `/admin/posts` --> - ``` diff --git a/docs/api/route.mdx b/docs/api/route.mdx index aee676feb2..60191b2592 100644 --- a/docs/api/route.mdx +++ b/docs/api/route.mdx @@ -30,6 +30,121 @@ Note: this component should only be used with vanilla and Stencil JavaScript pro ::: +## Catch-all Routes {/* #catch-all-routes */} + +:::note + +Catch-all routes are supported starting in Ionic v9.1. In earlier versions, `url="/:any"` can be used to catch paths whose first segment does not match another route. Unknown paths below a route that does match, such as `/admin/settings`, still render that route. + +::: + +A `url` that ends in `*` is a catch-all route. It matches any remaining path, which is useful for showing a "not found" page. The part of the path matched by `*` is passed to the component as the `pathMatch` property, without a leading `/`. + +```html + + + + + + + +``` + +With these routes: + +| Path | Renders | `pathMatch` | +| --------------------- | ---------------------- | -------------- | +| `/admin` | `page-admin` | | +| `/admin/users/1` | `page-admin-user` | | +| `/admin/settings` | `page-admin-not-found` | `settings` | +| `/admin/users/1/edit` | `page-admin-not-found` | `users/1/edit` | +| `/settings` | `page-not-found` | `settings` | + +The component reads `pathMatch` like any other property: + + + + + +```javascript +customElements.define( + 'page-not-found', + class extends HTMLElement { + connectedCallback() { + this.innerHTML = ` + +

No page found at /${this.pathMatch}

+
+ `; + } + } +); +``` + +
+ + + +```typescript +import { Component, Prop, h } from '@stencil/core'; + +@Component({ + tag: 'page-not-found', +}) +export class PageNotFound { + @Prop() pathMatch?: string; + + render() { + return ( + +

No page found at /{this.pathMatch}

+
+ ); + } +} +``` + +
+ +
+ +### Matching Priority {/* #matching-priority */} + +Without a catch-all, a route that has a `url` and no child routes also matches paths that have extra segments after its `url`. Routes with an empty `url`, such as `/` or a default child route, only match exactly. When a catch-all route matches the path, routes that only match by ignoring extra segments are no longer considered, even if another route wins. + +When a catch-all route and a route without a catch-all both match the whole path, the route with more fixed segments wins. A fixed segment is one that is not a parameter, such as `admin`. If they have the same number, the route without the catch-all wins. + +Using the routes from the example above: + +| Path | Without catch-alls | With catch-alls | Why | +| --------------------- | ------------------ | ---------------------- | --------------------------------------------------------------------------------------------- | +| `/admin` | `page-admin` | `page-admin` | `/admin` and `/admin/*` both have one fixed segment, so the route without the catch-all wins. | +| `/admin/users/1` | `page-admin-user` | `page-admin-user` | `/admin/users/:id` has more fixed segments than `/admin/*`. | +| `/admin/settings` | `page-admin` | `page-admin-not-found` | `page-admin` only matched by ignoring `settings`, so the catch-all wins. | +| `/admin/users/1/edit` | `page-admin-user` | `page-admin-not-found` | `page-admin-user` only matched by ignoring `edit`, so the catch-all wins. | +| `/settings` | No match | `page-not-found` | No other route matches. | + +When more than one catch-all route matches, the one with the most fixed segments wins. Segments from parent routes count toward this total, so a catch-all route nested inside another route can handle unknown paths below it. + +Redirects run before routes are matched. A redirect from `*` applies to every path, so `` takes precedence over ``. + +### Caveats {/* #caveats */} + +A catch-all route also matches its own prefix, so `/admin/*` matches `/admin` with `pathMatch` set to an empty string. This differs from [`ion-route-redirect`](route-redirect.mdx), where `from="/admin/*"` does not match `/admin`. + +A `*` is only treated as a wildcard when it is the last segment of the `url`. On a route with child routes, the `*` is ignored and the route matches as if its `url` did not end in `*`. + +The router sets `pathMatch` on catch-all routes and overwrites any value passed in `componentProps`, so do not pass it there. + +If the `component` of a catch-all route is a tab in `ion-tabs`, the tab does not receive `pathMatch`. Instead, nest the catch-all route as a child of the tab's route so it renders in that tab's `ion-router-outlet`: + +```html + + + + +``` + ## Navigation Hooks {/* #navigation-hooks */} Navigation hooks can be used to perform tasks or act as navigation guards. Hooks are used by providing functions to the `beforeEnter` and `beforeLeave` properties on each `ion-route`. Returning `true` allows navigation to proceed, while returning `false` causes it to be cancelled. Returning an object of type `NavigationHookOptions` allows you to redirect navigation to another page. diff --git a/docs/api/router.mdx b/docs/api/router.mdx index d9cc1cd60d..1b5262397d 100644 --- a/docs/api/router.mdx +++ b/docs/api/router.mdx @@ -77,8 +77,12 @@ interface RouterCustomEvent extends CustomEvent { ## Usage {/* #usage */} +The last route is a [catch-all route](route.mdx#catch-all-routes) that renders `page-not-found` for any URL that no other route matches. + ```html + + @@ -100,6 +104,7 @@ interface RouterCustomEvent extends CustomEvent { + ```