Skip to content

devTech-zhang/react-vt-router

English | 简体中文

react-vt-router

npm version bundle size license

A tiny React router built on native Web APIs: History, URLPattern, View Transitions (optionally Navigation API). It mirrors the core of react-router while staying small and fast.

Install

npm install react-vt-router
# or
pnpm add react-vt-router
# or
yarn add react-vt-router

Quick start

  1. Layout with nav and Outlet
import { Link, NavLink, Outlet } from "react-vt-router";

function Layout() {
  return (
    <div>
      <nav style={{ display: "flex", gap: 12 }}>
        <Link to="/">Home</Link>
        <NavLink
          to="/users"
          className={({ isActive }) => (isActive ? "active" : undefined)}
        >
          Users
        </NavLink>
      </nav>
      <hr />
      <Outlet />
    </div>
  );
}
  1. Pages: params and query
import { Link, useParams, useSearchParams } from "react-vt-router";

function Home() {
  return <h2>Home</h2>;
}

function Users() {
  const list = [1, 2, 3];
  return (
    <div>
      <h2>Users</h2>
      <ul>
        {list.map(id => (
          <li key={id}>
            <Link to={`/users/${id}?tab=profile`}>User {id}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

function UserDetail() {
  const { id } = useParams<{ id: string }>();
  const [sp] = useSearchParams();
  return (
    <div>
      <h3>User: {id}</h3>
      <div>tab = {sp.get("tab") ?? "-"}</div>
    </div>
  );
}

function NotFound() {
  return <h2>404</h2>;
}
  1. Wire up routes
import { BrowserRouter, Routes, Route, Navigate } from "react-vt-router";

export default function App() {
  return (
    <BrowserRouter defaultViewTransition="zoom">
      <Routes>
        <Route path="/" element={<Layout />}>           {/* parent route with layout */}
          <Route index element={<Home />} />              {/* index = default child */}
          <Route path="users">
            <Route index element={<Users />} />
            <Route path=":id" element={<UserDetail />} />
          </Route>
          {/* redirect old path */}
          <Route path="old-home" element={<Navigate to="/" replace />} />
        </Route>
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}
  1. Programmatic navigation and View Transitions
import { useNavigate } from "react-vt-router";

function Buttons() {
  const navigate = useNavigate();
  return (
    <div style={{ display: "flex", gap: 8 }}>
      <button onClick={() => navigate("/users/1", { viewTransition: "slide" })}>
        Go to user 1 (slide)
      </button>
      <button onClick={() => navigate.back()}>Back</button>
      <button onClick={() => navigate.forward()}>Forward</button>
    </div>
  );
}
  1. Matching (optional)
import { useMatch } from "react-vt-router";

function UsersBadge() {
  const match = useMatch("/users/*");
  return match ? <span>In Users</span> : null;
}

Highlights

  • Built-in View Transition presets: fade | slide | slide-left | slide-right | zoom
  • URLPattern-based matching with robust string fallback
  • Familiar API surface close to react-router for core usage
  • TypeScript-first

Components

(Only core options are listed here.)

  • BrowserRouter(props)

    • enableNavigationAPI?: boolean – intercept Navigation API when available (default false)
    • defaultViewTransition?: boolean | string | ViewTransitionConfig
      • boolean: true to enable, false to disable
      • string: one of presets ("fade" | "slide" | "slide-left" | "slide-right" | "zoom")
      • ViewTransitionConfig: { name?, className?, attribute?, onStart?, onReady?, onFinished? }
  • Routes / Route

    • RouteProps: { path?: string; index?: boolean; element?: ReactNode; caseSensitive?: boolean; redirectTo?: string }
  • Link(props)

    • { to: string; replace?: boolean; viewTransition?: boolean | string | ViewTransitionConfig; scroll?: ScrollBehavior }
  • NavLink(props)

    • className?: string | (({ isActive, isPending }) => string)
    • style?: CSSProperties | (({ isActive, isPending }) => CSSProperties)
    • end?: boolean
  • Navigate(props)

    • { to: string | number; replace?: boolean; state?: any; viewTransition?: boolean | string | ViewTransitionConfig }
  • Outlet / OutletWithContext

Hooks

  • useNavigate(): NavigateFunction
    • call: navigate(to, options?) (options: { replace?, state?, viewTransition?, scroll? })
    • helpers: navigate.back(), navigate.forward(), navigate.go(delta)
  • useLocation(): { pathname, search, hash, state, key }
  • useParams(): T (merged across nesting)
  • useSearchParams(): [URLSearchParams, set(next, options?)]
  • useMatch(pattern: string): { params, pathname, pattern } | null
  • useRoutes(routeObjects: RouteObject[]): ReactNode
  • useOutletContext(): T
  • useResolvedPath(to: string): string

View Transitions

  • Global: <BrowserRouter defaultViewTransition="zoom"/>
  • Per navigation: via Link or navigate(to, { viewTransition })
  • Presets: fade, slide, slide-left, slide-right, zoom
  1. Global default config
import { BrowserRouter } from "react-vt-router";

const vt = {
  name: "fade",            // will set attribute="fade" on <html>
  className: "vt-running", // class added on <html> while transition runs
  attribute: "data-vt",    // attribute name, defaults to data-vt
  onStart() { /* before snapshot */ },
  onReady() { /* snapshot done, render new page */ },
  onFinished() { /* end of transition */ },
};

export function App() {
  return (
    <BrowserRouter defaultViewTransition={vt}>
      {/* ...Routes */}
    </BrowserRouter>
  );
}
  1. Per-navigation override (Link / navigate)
import { Link, useNavigate } from "react-vt-router";

// Link: preset string
<Link to="/users/2" viewTransition="slide-right">User 2</Link>

// Link: explicitly disable this transition
<Link to="/" viewTransition={false}>Back home (no animation)</Link>

// Link: custom object
<Link to="/about" viewTransition={{ name: "fade", className: "my-vt" }}>About</Link>

// Programmatic: pass object
function Go() {
  const navigate = useNavigate();
  return (
    <button
      onClick={() => navigate("/users/3", {
        viewTransition: { name: "slide", className: "vt-running" },
        replace: false,
        scroll: "auto",
      })}
    >
      To user 3 (custom VT)
    </button>
  );
}
  1. Lifecycle hooks
const vt = {
  name: "fade",
  onStart() { console.log("VT start"); },
  onReady() { console.log("VT ready"); },
  onFinished() { console.log("VT finished"); },
};
  1. CSS examples

During a transition the router adds to :

  • an attribute (default data-vt) with value = name
  • a class (configurable via className)

You can target them and also use the View Transitions pseudo-elements:

/* Scoped by name: only active for fade */
html[data-vt="fade"]::view-transition-old(root) {
  animation: fade-out 180ms ease-in both;
}
html[data-vt="fade"]::view-transition-new(root) {
  animation: fade-in 220ms ease-out both;
}

@keyframes fade-out { from { opacity: 1 } to { opacity: 0 } }
@keyframes fade-in  { from { opacity: 0 } to { opacity: 1 } }

/* Optional: global flag while running */
html.vt-running { /* ... */ }
  1. Advanced: directional transitions
import { useNavigate } from "react-vt-router";

function PrevNext({ currentId }: { currentId: number }) {
  const navigate = useNavigate();
  const go = (id: number) => {
    const vtName = id > currentId ? "slide-left" : "slide-right";
    navigate(`/users/${id}`, { viewTransition: vtName });
  };
  return (
    <div style={{ display: "flex", gap: 8 }}>
      <button onClick={() => go(currentId - 1)}>Prev</button>
      <button onClick={() => go(currentId + 1)}>Next</button>
    </div>
  );
}

Note: if View Transitions API is not available, navigation gracefully falls back to no animation. Passing viewTransition: false forces no animation for that navigation.

Debug & compatibility

  • Debug logs: window.__REACT_VT_ROUTER_DEBUG__ = true
  • Force string fallback (disable URLPattern): window.__REACT_VT_ROUTER_FORCE_STRING__ = true
  • Navigation API interception: set enableNavigationAPI on BrowserRouter

Notes & limitations

  • Best for small/medium SPAs: minimal API, zero deps, quick to adopt.
  • For large/complex apps requiring data APIs (loader/action/defer), SSR, hash/memory routers, deep caching, error/lazy boundaries, consider react-router.
  • Targets modern browsers; gracefully falls back when View Transitions / URLPattern are unavailable.

License

MIT

About

A tiny React router built on native Web APIs: History, URLPattern, View Transitions (optionally Navigation API). It mirrors the core of react-router while staying small and fast.

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors