Utilities
Helper functions for defining routes and managing state.
route()
Helper function to define routes with proper typing. The component always receives a params prop with types inferred from the path pattern. When a loader is defined, the component also receives a data prop. Components also receive state, setState, setStateSync, resetState, and resetStateSync props for navigation state management.
import { route } from "@funstack/router";
// Route without loader - component receives params prop
function ProfileTab({ params }: { params: { userId: string } }) {
return <div>Profile for user {params.userId}</div>;
}
// Route with loader - component receives both data and params props
function UserPage({
data,
params,
}: {
data: User;
params: { userId: string };
}) {
return <h1>{data.name} (ID: {params.userId})</h1>;
}
const myRoute = route({
path: "/users/:userId",
component: UserPage,
loader: async ({ params }) => {
return fetchUser(params.userId);
},
children: [
route({ path: "/profile", component: ProfileTab }),
route({ path: "/settings", component: SettingsTab }),
],
});
Options
| Option | Type | Description |
|---|
path | string (optional) | URL path pattern (supports :param syntax). If omitted, creates a pathless route that always matches and consumes no pathname. Useful for layout wrappers. |
component | ComponentType | React component to render. Receives params prop (and data prop if loader is defined) |
action | (args: ActionArgs) => T | Function to handle form submissions (POST navigations). Receives a Request with FormData body. The return value is passed to the loader as actionResult. |
loader | (args: LoaderArgs) => T | Function to load data. May be synchronous or asynchronous |
children | RouteDefinition[] | Nested child routes |
exact | boolean | Override default matching. true = exact match only, false = prefix match. Defaults to true for leaf routes, false for parent routes. |
requireChildren | boolean | Whether a parent route requires a child to match. true (default) = parent only matches if a child matches, false = parent can match alone with outlet as null. Enables catch-all routes to work intuitively. |
routeState<TState>()
Curried helper function for defining routes with typed navigation state. Use this when your route component needs to manage state that persists across browser back/forward navigation.
import { routeState, type RouteComponentProps } from "@funstack/router";
type PageState = { scrollPosition: number; selectedTab: string };
function UserPage({
params,
state,
setState,
setStateSync,
resetState,
}: RouteComponentProps<{ userId: string }, PageState>) {
// state is PageState | undefined (undefined on first visit)
const scrollPosition = state?.scrollPosition ?? 0;
const selectedTab = state?.selectedTab ?? "posts";
// Async state update (recommended for most cases)
const handleTabChange = async (tab: string) => {
await setState({ scrollPosition, selectedTab: tab });
};
// Sync state update (for immediate updates like scroll position)
const handleScroll = (position: number) => {
setStateSync({ scrollPosition: position, selectedTab });
};
return (
<div>
<h1>User {params.userId}</h1>
<button onClick={() => resetState()}>Clear State</button>
</div>
);
}
// Use routeState<TState>() for typed state management
const userRoute = routeState<PageState>()({
path: "/users/:userId",
component: UserPage,
});
// With loader
const productRoute = routeState<{ filter: string }>()({
path: "/products",
component: ProductList,
loader: async () => fetchProducts(),
});
How It Works
Navigation state is stored in the browser's Navigation API. The router provides two methods to update state:
setState - Async method that uses replace navigation. Returns a Promise that resolves when the navigation completes. Because it performs a navigation, the update is wrapped in a React transition (may set isPending to true).setStateSync - Sync method that uses navigation.updateCurrentEntry(). Updates state immediately without waiting. This is not a navigation, so it bypasses React transitions entirely (isPending stays false).
Navigation state characteristics:
- State persists when navigating back/forward in history
- Each history entry has its own independent state
- State must be serializable (no functions, Symbols, or DOM nodes)
Internal Storage
The router stores state internally using a __routeStates array indexed by route match position. This enables each nested route to maintain independent state:
// Internal structure stored in NavigationHistoryEntry
{
__routeStates: [
{ sidebarOpen: true }, // Layout (index 0)
{ selectedTab: "posts" }, // UserPage (index 1)
{ scrollY: 500 }, // PostsPage (index 2)
]
}
hardReload()
Performs a full page reload, bypassing the router's navigation interception entirely. This is useful when you need a true browser reload that the router should not intercept.
import { hardReload } from "@funstack/router";
// Full page reload — bypasses the router and all blockers
hardReload();
hardNavigate(url)
Performs a full page navigation to the given URL, bypassing the router's navigation interception entirely. This triggers a real browser navigation instead of a client-side route change, and skips all blockers.
import { hardNavigate } from "@funstack/router";
// Full page navigation — bypasses the router and all blockers
hardNavigate("/other-page");
Parameters
| Parameter | Type | Description |
|---|
url | string | The URL to navigate to |
Server Entry Point
The route() and routeState() helpers are also available from a server-compatible entry point. Use this when defining routes in React Server Components or other server-side code.
// In Server Components or server-side route definitions
import { route, routeState } from "@funstack/router/server";
// Define routes without the "use client" directive
const routes = [
route({
path: "/",
component: HomePage,
}),
routeState<{ tab: string }>()({
path: "/dashboard",
component: DashboardPage,
}),
];
When to Use
- Defining routes in React Server Components
- Server-side route configuration files
- Any context where
"use client" would cause issues
Available Exports
The @funstack/router/server entry point exports:
route - Route definition helperrouteState - Route definition helper with typed statebindRoute - Binds a component to a partial route definition (see Two-Phase Route Definitions)- Types:
ActionArgs, LoaderArgs, RouteDefinition, PathParams, RouteComponentProps, RouteComponentPropsWithData
For client-side features like Router, Outlet, and hooks, use the main @funstack/router entry point.
See the React Server Components guide for a full walkthrough of using the server entry point, and the Two-Phase Route Definitions guide for using bindRoute() to split route definitions across the RSC boundary.