@real-router/preact
v0.2.0
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/browser-pluginPeer 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, toRoute, fromRoute } | On transition start/end |
// 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" />;
}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><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 |
| 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>;<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) => analytics.track("nav_error", { code: error.code })}
>
<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.
Documentation
Full documentation: Wiki
- RouterProvider · RouteView · RouterErrorBoundary · Link
- useRouter · useRoute · useRouteNode · useNavigator · useRouteUtils · useRouterTransition
Examples
11 runnable examples — each is a standalone Vite app. Run: cd examples/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.
