npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@real-router/preact

v0.11.1

Published

Preact integration for Real-Router

Readme

@real-router/preact

License: MIT

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, keepAlive is 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

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.

License

MIT © Oleg Ivanov