what-router
v0.13.8
Published
What Framework - File-based & programmatic router with View Transitions
Downloads
3,562
Maintainers
Readme
what-router
Client-side router for What Framework. Supports dynamic routes, nested layouts, route groups, middleware, View Transitions API, scroll restoration, and prefetching.
Install
npm install what-router what-coreOr use via the main package:
import { Router, Link, navigate } from 'what-framework/router';Quick Start
import { mount } from 'what-framework';
import { Router, Link, navigate } from 'what-router';
function Home() {
return <h1>Home</h1>;
}
function User({ params }) {
return <h1>User {params.id}</h1>;
}
function App() {
return (
<div>
<nav>
<Link href="/">Home</Link>
<Link href="/users/1">User 1</Link>
</nav>
<Router
routes={[
{ path: '/', component: Home },
{ path: '/users/:id', component: User },
]}
/>
</div>
);
}
mount(<App />, '#app');Route Patterns
{ path: '/users/:id', component: User } // Dynamic param
{ path: '/docs/*', component: DocsLayout } // Catch-all
{ path: '/blog/[slug]', component: Post } // File-based syntax
{ path: '/[...rest]', component: CatchAll } // Named catch-allNavigation
import { navigate, route } from 'what-router';
// Programmatic navigation
navigate('/dashboard');
navigate('/login', { replace: true });
navigate('/page', { transition: false }); // skip View Transition
// Reactive route state
route.path; // current path
route.params; // { id: '123' }
route.query; // { page: '1' }
route.hash; // '#section'
route.isNavigating;Link Component
<Link href="/about">About</Link>
<Link href="/about" activeClass="active" exactActiveClass="exact-active">About</Link>
<Link href="/about" replace prefetch={false}>About</Link>Links automatically get active and exact-active CSS classes based on the current route. Hover prefetching is enabled by default.
Nested Layouts
import { defineRoutes, nestedRoutes, Outlet } from 'what-router';
function DashboardLayout({ children }) {
return (
<div>
<Sidebar />
<main>{children}</main>
</div>
);
}
const routes = [
...nestedRoutes('/dashboard', [
{ path: '/', component: DashboardHome },
{ path: '/settings', component: Settings },
], { layout: DashboardLayout }),
];Route Guards & Middleware
import { guard, asyncGuard } from 'what-router';
// Sync guard
const requireAuth = guard(
() => isLoggedIn(),
'/login' // redirect on failure
);
const ProtectedPage = requireAuth(Dashboard);
// Async guard
const requireRole = asyncGuard(
async () => await checkPermission('admin'),
{ fallback: '/unauthorized', loading: Spinner }
);
// Route-level middleware
{
path: '/admin',
component: AdminPanel,
middleware: [authMiddleware, roleMiddleware],
}Route middleware is synchronous: return true or nothing to continue,
false to show the Router fallback (403 by default), or a path string to
redirect. Returning a promise or thenable throws ERR_ASYNC_MIDDLEWARE before
the protected component mounts. The Router observes rejections but does not
await or use async middleware results.
For async authorization, wrap the route component instead:
{
path: '/admin',
component: asyncGuard(check, {
fallback: '/unauthorized',
loading: Spinner,
})(AdminPanel),
}
// Without custom options: component: asyncGuard(check)(AdminPanel)Client-side guards control rendering and navigation; enforce authorization separately on the server.
redirect() throws a navigation signal. Two places catch it: route middleware,
and a component body.
{
path: '/admin',
component: AdminPanel,
middleware: [() => isLoggedIn() || redirect('/login')],
}
function Private() {
if (!isLoggedIn()) redirect('/login');
return h(Secret, {});
}From an event handler, a promise callback or a timer, nothing catches the
signal and it surfaces as an uncaught error carrying ERR_REDIRECT_NOT_CAUGHT.
Call navigate(to) there instead. A try/catch around a redirect() call
also swallows the signal, so rethrow anything whose name is RouterRedirect.
View Transitions
Navigation uses the View Transitions API by default when available. Use helpers to customize:
import { viewTransitionName, setViewTransition } from 'what-router';
// Name elements for transitions
<img {...viewTransitionName('hero-image')} src={url} />
// Set transition type
setViewTransition('slide');Scroll Restoration
import { enableScrollRestoration } from 'what-router';
enableScrollRestoration(); // call once at app entryAPI
| Export | Description |
|---|---|
| Router | Route matching component |
| Link / NavLink | Navigation link with active states |
| navigate(to, opts?) | Programmatic navigation |
| route | Reactive route state object |
| useRoute() | Hook returning computed route properties |
| useParams() | Current route params |
| useSearch() | Parsed query string of the last matched route (stale on a 404, like route.query) |
| useNavigate() | Returns navigate |
| redirect(to, opts?) | Abort route matching and navigate, from route middleware (throws, never returns) |
| prefetchRoute(href) | Prefetch a route's assets |
| beforeNavigate(fn) | Guard run before each route navigation (not hash links); return false to cancel |
| afterNavigate(fn) | Callback run after each committed navigation |
| defineRoutes(config) | Create routes from flat object |
| nestedRoutes(base, children, opts?) | Nested route helper |
| routeGroup(name, routes, opts?) | Group routes without affecting URL |
| guard(check, fallback) | Sync route guard |
| asyncGuard(check, opts?) | Async route guard |
| Redirect | Redirect component |
| Outlet | Nested route outlet |
| FileRouter | File-based router component |
| prefetch(href) | Prefetch a route's assets |
| enableScrollRestoration() | Enable scroll position restoration |
| viewTransitionName(name) | View Transition name helper |
| setViewTransition(type) | Set View Transition type |
Links
License
MIT
