@real-router/preact
v0.11.1
Published
Preact integration for Real-Router
Maintainers
Readme
@real-router/preact
Preact integration for Real-Router — hooks, components, and context providers.
Installation
npm install @real-router/preact @real-router/core@real-router/core is the only hard dependency. Add @real-router/browser-plugin
(or hash-plugin / navigation-plugin / memory-plugin) when you need History
API integration — the Quick Start below uses it.
Peer dependency: preact >= 10.0.0
Quick Start
import { createRouter } from "@real-router/core";
import { browserPluginFactory } from "@real-router/browser-plugin";
import { RouterProvider, RouteView, Link } from "@real-router/preact";
const router = createRouter([
{ name: "home", path: "/" },
{
name: "users",
path: "/users",
children: [{ name: "profile", path: "/:id" }],
},
]);
router.usePlugin(browserPluginFactory());
router.start();
function App() {
return (
<RouterProvider router={router}>
<nav>
<Link routeName="home">Home</Link>
<Link routeName="users">Users</Link>
</nav>
<RouteView nodeName="">
<RouteView.Match segment="home">
<HomePage />
</RouteView.Match>
<RouteView.Match segment="users">
<UsersPage />
</RouteView.Match>
<RouteView.NotFound>
<NotFoundPage />
</RouteView.NotFound>
</RouteView>
</RouterProvider>
);
}Hooks
| Hook | Returns | Re-renders |
| ----------------------- | ---------------------------------------------------------- | --------------------------------------- |
| useRouter() | Router | Never |
| useNavigator() | Navigator | Never (stable ref, safe to destructure) |
| useRoute() | { navigator, route, previousRoute } | Every navigation |
| useRouteNode(name) | { navigator, route, previousRoute } | Only when node activates/deactivates |
| useRouteUtils() | RouteUtils | Never |
| useRouterTransition() | { isTransitioning, isLeaveApproved, toRoute, fromRoute } | On transition start/end |
| useRouteExit(handler, options?) | void — wraps router.subscribeLeave with abort + same-route guards | Never (stable subscription) |
| useRouteEnter(handler, options?) | void — fires on nav-driven mount via useRoute() snapshot | Never (handler stays current) |
// useRouteNode — re-renders only when "users.*" changes
function UsersLayout() {
const { route } = useRouteNode("users");
if (!route) return null;
switch (route.name) {
case "users":
return <UsersList />;
case "users.profile":
return <UserProfile id={route.params.id} />;
default:
return null;
}
}
// useNavigator — stable reference, never causes re-renders
function BackButton() {
const navigator = useNavigator();
return <button onClick={() => navigator.navigate("home")}>Back</button>;
}
// useRouterTransition — progress bars, loading states
function GlobalProgress() {
const { isTransitioning } = useRouterTransition();
if (!isTransitioning) return null;
return <div className="progress-bar" />;
}
// useRouteExit — exit animations, draft autosave, AbortSignal-aware cleanup
function FadeOut() {
const ref = useRef<HTMLDivElement>(null);
useRouteExit(async ({ signal }) => {
const el = ref.current;
if (!el) return;
el.classList.add("fade-out");
const cleanup = () => el.classList.remove("fade-out");
signal.addEventListener("abort", cleanup, { once: true });
el.getBoundingClientRect(); // style flush
await Promise.allSettled(el.getAnimations().map((a) => a.finished));
cleanup();
});
return <div ref={ref}>...</div>;
}
// useRouteEnter — page-enter analytics, focus management, entry animations
function PageEnterAnalytics() {
useRouteEnter(({ route, previousRoute }) => {
analytics.track("page_enter", {
route: route.name,
from: previousRoute.name,
});
});
return null;
}Components
<Link>
Navigation link with automatic active state detection. Re-renders only when its active status changes.
<Link
routeName="users.profile"
routeParams={{ id: "123" }}
activeClassName="active" // default: "active"
activeStrict={false} // default: false (ancestor match)
ignoreQueryParams={true} // default: true
routeOptions={{ replace: true }}
>
View Profile
</Link>hash prop — URL fragment / tab-style UIs
<Link routeName="settings" hash="profile">Profile</Link>
<Link routeName="settings" hash="account">Account</Link>Tri-state: undefined preserves the current hash, "" clears it, a value sets it. Active class is hash-aware — only the matching tab lights up. Live demo: examples/web/react/link-hash/ — behavior is identical across adapters, only template syntax differs. See the Hash Fragment Support wiki page for the full surface.
<RouteView>
Declarative route matching. Renders the first matching <RouteView.Match> child.
<RouteView nodeName="">
<RouteView.Match segment="users">
<UsersPage />
</RouteView.Match>
<RouteView.Match segment="settings">
<SettingsPage />
</RouteView.Match>
<RouteView.NotFound>
<NotFoundPage />
</RouteView.NotFound>
</RouteView>Note: Unlike the React adapter,
keepAliveis not supported. Preact has no equivalent of React's<Activity>API. Components unmount completely when navigating away.
RouteView.Match props
| Prop | Type | Description |
| ---------- | ------------------- | ----------------------------------------------------------------------------- |
| segment | string | Route segment to match |
| exact | boolean | When true, matches only the exact route (not descendants). Default: false |
| fallback | ComponentChildren | Shown while children suspend. Wraps children in <Suspense> when provided. |
Lazy loading with fallback (experimental)
Preact's lazy and Suspense come from preact/compat. Support is experimental — test before shipping to production.
import { lazy } from "preact/compat";
const LazyDashboard = lazy(() => import("./Dashboard"));
<RouteView nodeName="">
<RouteView.Match segment="dashboard" fallback={<Spinner />}>
<LazyDashboard />
</RouteView.Match>
</RouteView>;Advanced exports
For custom integrations (e.g., writing your own hook on top of the router context), the low-level contexts are also exported:
import {
RouterContext, // Raw Router instance
NavigatorContext, // Navigator (stable ref)
RouteContext, // { navigator, route, previousRoute }
type RouteViewProps,
type RouteViewMatchProps,
type RouteViewNotFoundProps,
} from "@real-router/preact";Most apps should prefer the use* hooks above over consuming contexts directly.
<RouterErrorBoundary>
Declarative error handling for navigation errors. Shows a fallback alongside children (not instead of) when a guard rejects or a route is not found.
import { RouterErrorBoundary } from "@real-router/preact";
<RouterErrorBoundary
fallback={(error, resetError) => (
<div className="toast">
{error.code} <button onClick={resetError}>Dismiss</button>
</div>
)}
onError={(error, toRoute, fromRoute) =>
analytics.track("nav_error", {
code: error.code,
to: toRoute?.name,
from: fromRoute?.name,
})
}
>
<Link routeName="protected">Go to Protected</Link>
</RouterErrorBoundary>;Auto-resets on next successful navigation. Works with both <Link> and imperative router.navigate().
Accessibility
Enable screen reader announcements for route changes:
<RouterProvider router={router} announceNavigation>
{/* Your app */}
</RouterProvider>When enabled, a visually hidden aria-live region announces each navigation. Focus moves to the first <h1> on the new page. See Accessibility guide for details.
Scroll Restoration
Opt-in preservation of scroll position across navigations:
<RouterProvider router={router} scrollRestoration={{ mode: "restore" }}>
{/* Your app */}
</RouterProvider>Restores scroll on back/forward, scrolls to top (or #hash) on push. Three modes: "restore" (default), "top", "native". Custom containers via scrollContainer: () => HTMLElement | null. Lifecycle tied to the provider — created on mount, destroyed on unmount. See Scroll Restoration guide for details.
View Transitions
Opt-in animated route transitions via the browser's View Transitions API:
<RouterProvider router={router} viewTransitions>
{/* Your app */}
</RouterProvider>No-op on unsupported browsers (Firefox as of 2026-04, SSR). Customization is pure CSS via ::view-transition-* pseudo-elements and view-transition-name for hero morphs. See View Transitions guide for patterns.
Documentation
Full documentation: Wiki
- RouterProvider · RouteView · RouterErrorBoundary · Link · Scroll Restoration · View Transitions
- useRouter · useRoute · useRouteNode · useNavigator · useRouteUtils · useRouterTransition · useRouteExit · useRouteEnter
Examples
11 runnable examples — each is a standalone Vite app. Run: cd examples/web/preact/basic && pnpm dev
basic · nested-routes · auth-guards · data-loading · lazy-loading · async-guards · hash-routing · persistent-params · error-handling · dynamic-routes · combined
Related Packages
| Package | Description |
| ---------------------------------------------------------------------------------------- | ------------------------------------ |
| @real-router/core | Core router (required dependency) |
| @real-router/browser-plugin | Browser History API integration |
| @real-router/sources | Subscription layer (used internally) |
| @real-router/route-utils | Route tree queries (useRouteUtils) |
Contributing
See contributing guidelines for development setup and PR process.
