FUNSTACK Router

HomeGetting StartedLearnAPI ReferenceExamplesFAQ
GitHub

Built with @funstack/router — A modern React router based on the Navigation API

API Reference

ComponentsHooksUtilitiesTypes

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

OptionTypeDescription
pathstring (optional)URL path pattern (supports :param syntax). If omitted, creates a pathless route that always matches and consumes no pathname. Useful for layout wrappers.
componentComponentTypeReact component to render. Receives params prop (and data prop if loader is defined)
action(args: ActionArgs) => TFunction 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) => TFunction to load data. May be synchronous or asynchronous
childrenRouteDefinition[]Nested child routes
exactbooleanOverride default matching. true = exact match only, false = prefix match. Defaults to true for leaf routes, false for parent routes.
requireChildrenbooleanWhether 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

ParameterTypeDescription
urlstringThe 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 helper
  • routeState - Route definition helper with typed state
  • bindRoute - 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.