RouteView - greydragon888/real-router GitHub Wiki
-
Name:
RouteView -
Purpose: Declarative route matching component with optional state preservation via
keepAlive -
When to use: For rendering content based on the active route segment without manual
switch/case -
Availability: All 6 framework adapters —
@real-router/react(React 19.2+ forkeepAlivevia Activity),@real-router/preact(withoutkeepAlive),@real-router/solid(withoutkeepAlive),@real-router/vue(withkeepAlivevia native<KeepAlive>),@real-router/svelte(named snippets pattern instead of<Match>/<NotFound>), and@real-router/angular. Not available in@real-router/react/legacy.
import { RouteView } from "@real-router/react";
// or
import { RouteView } from "@real-router/preact";
// or
import { RouteView } from "@real-router/solid";
// or
import { RouteView } from "@real-router/vue";
// or
import { RouteView } from "@real-router/svelte";
// Angular
import { RouteView, RouteMatch, RouteNotFound } from "@real-router/angular";
function App() {
return (
<RouteView nodeName="">
<RouteView.Match segment="users">
<UsersPage />
</RouteView.Match>
<RouteView.Match segment="settings">
<SettingsPage />
</RouteView.Match>
<RouteView.NotFound>
<NotFoundPage />
</RouteView.NotFound>
</RouteView>
);
}| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
nodeName |
string |
Yes | — | Route tree node to subscribe to. "" for root. |
children |
ReactNode |
Yes | — |
<RouteView.Match> and <RouteView.NotFound> elements |
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
segment |
string |
Yes | — | Route segment to match against |
exact |
boolean |
No | false |
Exact match only (no descendants) |
keepAlive |
boolean |
No | false |
Preserve component state when deactivated (React Activity) |
fallback |
ReactNode |
No | — | Fallback UI while content is loading (wrapped in Suspense) |
children |
ReactNode |
Yes | — | Content to render when matched |
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
ReactNode |
Yes | — | Content to render on UNKNOWN_ROUTE
|
-
Type:
ReactNode -
Description: Only
<RouteView.Match>and<RouteView.NotFound>elements are recognized. Other elements are traversed for nested Match/NotFound (e.g., inside<Fragment>). Non-element children are ignored.
<RouteView nodeName="">
<RouteView.Match segment="home">
<HomePage />
</RouteView.Match>
{/* Fragment wrappers are transparent — Match inside is found */}
<>
<RouteView.Match segment="about">
<AboutPage />
</RouteView.Match>
</>
</RouteView>The fallback prop wraps matched content in a <Suspense> boundary, enabling async component loading:
React/Preact:
<RouteView.Match segment="users" fallback={<Spinner />}>
<UsersPage /> {/* Wrapped in <Suspense fallback={<Spinner />}> */}
</RouteView.Match>Solid:
<Match segment="users" fallback={<Spinner />}>
<UsersPage /> {/* Wrapped in <Suspense fallback={<Spinner />}> */}
</Match>Vue:
<RouteView.Match segment="users" :fallback="Spinner">
<UsersPage /> <!-- Wrapped in <Suspense> -->
</RouteView.Match>Svelte:
<Lazy loader={async () => (await import('./UsersPage')).default} fallback={Spinner} />Svelte uses a different pattern -- <Lazy> component inside snippets instead of a fallback prop.
Angular:
<!-- Angular: uses ng-template with directives instead of sub-components -->
<route-view [routeNode]="''">
<ng-template routeMatch="home"><app-home /></ng-template>
<ng-template routeMatch="users"><app-users /></ng-template>
<ng-template routeNotFound><app-not-found /></ng-template>
</route-view>Angular note: The input is
routeNode(aliased fromnodeName) becausenodeNameis a read-only property onHTMLElement. Route matching usesng-templatewithrouteMatchandrouteNotFoundstructural directives collected viacontentChildren.
- HTML element: Does not render its own DOM element
- Output: A fragment containing matched children
RouteView subscribes to a route node via useRouteNode(nodeName) and matches children against the active route:
- Collects all
<RouteView.Match>and<RouteView.NotFound>from children (recursively unwraps fragments) - Iterates Match elements — first match wins (subsequent matches are skipped unless
keepAlive) - Segment matching: constructs
fullSegmentNameasnodeName.segment(or justsegmentifnodeNameis"") - Default matching uses
startsWithSegment(prefix match). Withexact, uses strict equality.
Only the first matched segment is rendered. All others return null.
// Route: "users.profile"
<RouteView nodeName="">
<RouteView.Match segment="users">
{" "}
{/* Rendered (prefix match) */}
<UsersPage />
</RouteView.Match>
<RouteView.Match segment="settings">
{" "}
{/* null */}
<SettingsPage />
</RouteView.Match>
</RouteView>Active match is rendered as <Activity mode="visible">. Previously active keepAlive matches are rendered as <Activity mode="hidden"> — hidden but not unmounted.
// Route: "settings", previously visited "users"
<RouteView nodeName="">
<RouteView.Match segment="users" keepAlive>
{" "}
{/* <Activity mode="hidden"> */}
<UsersPage />
</RouteView.Match>
<RouteView.Match segment="settings" keepAlive>
{" "}
{/* <Activity mode="visible"> */}
<SettingsPage />
</RouteView.Match>
</RouteView>React (Activity):
keepAlive |
Route matches | Previously activated | Result |
|---|---|---|---|
true |
Yes | — | <Activity mode="visible"> |
true |
No | Yes | <Activity mode="hidden"> |
true |
No | No |
null (lazy mount) |
false |
Yes | — | <Fragment>{children}</Fragment> |
false |
No | — | null |
Vue (native KeepAlive):
keepAlive |
Route matches | Result |
|---|---|---|
root true
|
Yes | <KeepAlive><Wrapper>...</Wrapper></KeepAlive> |
root true
|
No | Vue <KeepAlive> caches the inactive instance |
| per-match | Yes | <KeepAlive><Wrapper>...</Wrapper></KeepAlive> |
| per-match | No | Empty <KeepAlive> placeholder preserves cache |
false |
Yes | <Fragment>{children}</Fragment> |
false |
No | null |
<RouteView.NotFound> renders only when:
- No Match matched the active route AND
- The route name is
UNKNOWN_ROUTE
If multiple <RouteView.NotFound> are present, the last one is used.
Component does not render DOM elements and does not support styling directly. Style the children instead.
Component does not directly affect accessibility — it's a structural routing component. Ensure child components maintain proper focus management and ARIA attributes.
| Provider | Required | Description |
|---|---|---|
RouterProvider |
Yes | For accessing router via useRouteNode
|
-
useRouteNode(nodeName)— subscribes to the specified route node, re-renders only when that node's active/inactive state changes -
useRef— tracks whichkeepAlivesegments have been activated (lazy mount)
-
Activity(React 19.2+) — wrapskeepAlivematches for state preservation -
Children.toArray,isValidElement— child element collection -
Fragment— key-able wrapper for non-keepAlive matches
Component does not generate its own events. Route changes are handled by the router and propagated via useRouteNode.
- Renders the first matched
<RouteView.Match>based on the active route - Returns
nullifuseRouteNodereturns no route (node is inactive) - Supports
keepAlivefor preserving component state across navigations - Renders
<RouteView.NotFound>for unknown routes - Supports nesting — RouteView inside RouteView for hierarchical route trees
// Route: "users.profile"
// nodeName: ""
<RouteView.Match segment="users"> {/* Match: "users" is prefix of "users.profile" */}
<RouteView.Match segment="users" exact> {/* No match: "users" !== "users.profile" */}
<RouteView.Match segment="settings"> {/* No match: "settings" is not prefix */}Segment matching is dot-boundary safe: segment="users" does not match route "users2.list".
A keepAlive Match is not rendered until its route has been activated at least once. This prevents pre-rendering all keepAlive segments on first load.
- Empty RouteView (no children) returns
null - Non-Match/non-NotFound children are ignored (but traversed for nested Match/NotFound)
- When route is
undefined(node inactive), returnsnull— allkeepAlivehidden elements are also unmounted - Multiple
<RouteView.NotFound>— last one wins
- First match wins — only one Match is
visibleat a time -
keepAlivestate survives navigation within the same RouteView - Dot-boundary safe segment matching via
startsWithSegmentfrom@real-router/route-utils
| Component/Hook | Relationship |
|---|---|
useRouteNode |
Used internally for route node subscription |
RouterProvider |
Required provider for router context |
Link |
Navigation component — triggers route changes |
useRoute |
Alternative: full route state subscription |
import { RouteView } from "@real-router/react";
function App() {
return (
<RouteView nodeName="">
<RouteView.Match segment="home">
<HomePage />
</RouteView.Match>
<RouteView.Match segment="users">
<UsersPage />
</RouteView.Match>
<RouteView.NotFound>
<NotFoundPage />
</RouteView.NotFound>
</RouteView>
);
}React (Activity):
function App() {
return (
<RouteView nodeName="">
<RouteView.Match segment="users" keepAlive>
<UsersPage /> {/* State preserved when navigating away */}
</RouteView.Match>
<RouteView.Match segment="settings">
<SettingsPage /> {/* Unmounts normally */}
</RouteView.Match>
</RouteView>
);
}Vue (native KeepAlive):
Root-level keepAlive — all matches are kept alive:
<template>
<RouteView nodeName="" keepAlive>
<RouteView.Match segment="users">
<UsersPage />
<!-- State preserved when navigating away -->
</RouteView.Match>
<RouteView.Match segment="settings">
<SettingsPage />
<!-- Also preserved -->
</RouteView.Match>
</RouteView>
</template>Per-match keepAlive — only specific matches are kept alive:
<template>
<RouteView nodeName="">
<RouteView.Match segment="users" keepAlive>
<UsersPage />
<!-- State preserved when navigating away -->
</RouteView.Match>
<RouteView.Match segment="settings">
<SettingsPage />
<!-- Unmounts normally -->
</RouteView.Match>
</RouteView>
</template>When navigating users -> settings -> users:
-
<UsersPage />mounts (visible) - Navigate to
settings:<UsersPage />becomes hidden (not unmounted),<SettingsPage />mounts - Navigate back to
users:<UsersPage />becomes visible instantly — scroll position, form state, etc. preserved
function App() {
return (
<RouteView nodeName="">
<RouteView.Match segment="users" keepAlive>
<UsersLayout>
<RouteView nodeName="users">
<RouteView.Match segment="list" keepAlive>
<UsersList />
</RouteView.Match>
<RouteView.Match segment="profile">
<UserProfile />
</RouteView.Match>
</RouteView>
</UsersLayout>
</RouteView.Match>
</RouteView>
);
}keepAlive works independently at each nesting level.
<RouteView nodeName="users">
{/* Only matches "users.list" exactly, not "users.list.details" */}
<RouteView.Match segment="list" exact>
<UsersList />
</RouteView.Match>
{/* Matches "users.profile" and "users.profile.edit" */}
<RouteView.Match segment="profile">
<UserProfile />
</RouteView.Match>
</RouteView><RouteView nodeName="">
<RouteView.Match segment="users" keepAlive>
<UsersPage />
</RouteView.Match>
<RouteView.Match segment="settings" keepAlive>
<SettingsPage />
</RouteView.Match>
<RouteView.Match segment="about">
<AboutPage />
</RouteView.Match>
</RouteView>Both users and settings preserve state. about unmounts when deactivated.
<RouteView.Match segment="profile" keepAlive>
<UserProfile />
</RouteView.Match>;
function UserProfile() {
const { route } = useRouteNode("users.profile");
// route is defined when visible, undefined when hidden
// Component is NOT unmounted when hidden — state preserved
return <div>User: {route?.params.id}</div>;
}React:
function UsersList() {
useEffect(() => {
// Called when becoming visible
refreshData();
return () => {
// Called when hidden AND when unmounted
};
}, []);
return <List />;
}React Activity guarantees: visible -> hidden triggers effect cleanup, hidden -> visible re-runs effect setup.
Vue:
<script setup>
import { onActivated, onDeactivated } from "vue";
onActivated(() => {
// Called when becoming visible (KeepAlive reactivation)
refreshData();
});
onDeactivated(() => {
// Called when hidden (KeepAlive deactivation)
});
</script>
<template>
<List />
</template>Vue <KeepAlive> provides onActivated / onDeactivated lifecycle hooks for reacting to visibility changes.
// Don't use RouteView outside RouterProvider
function Bad() {
return (
<RouteView nodeName="">
<RouteView.Match segment="home">
<Home />
</RouteView.Match>
</RouteView>
); // Error: useRouteNode requires RouterProvider
}
// Wrap in RouterProvider
function Good() {
return (
<RouterProvider router={router}>
<RouteView nodeName="">
<RouteView.Match segment="home">
<Home />
</RouteView.Match>
</RouteView>
</RouterProvider>
);
}// Don't use render props — not supported
function Bad() {
return (
<RouteView.Match segment="users">
{(route) => <UsersPage route={route} />}
</RouteView.Match>
);
}
// Use hooks inside child components
function Good() {
return (
<RouteView.Match segment="users">
<UsersPage />
</RouteView.Match>
);
}
function UsersPage() {
const { route } = useRouteNode("users");
return <div>{route?.name}</div>;
}// Don't import from legacy entry — RouteView is not available
import { RouteView } from "@real-router/react/legacy"; // Error!
// Use main entry point (React 19.2+)
import { RouteView } from "@real-router/react";