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

@bgub/fig-tanstack-router

v0.1.2

Published

TanStack Router adapter for Fig

Readme

@bgub/fig-tanstack-router

The Fig framework adapter for TanStack Router. Route matching, loaders, navigation, and history come from @tanstack/router-core; this package adds the Fig components, hooks, native links, asset mapping, and reactive store bridge used by TanStack Start.

Generated file routes are the recommended interface. Code-created route trees remain supported for standalone use, but they are not the design center.

Installation

pnpm add @bgub/fig-tanstack-router @bgub/fig @bgub/fig-dom @tanstack/router-core

For TanStack Start, also install @bgub/fig-tanstack-start and use its Vite plugin and default entries. See the @bgub/fig-tanstack-start guide for the complete build and hydration setup.

Recommended: generated file routes

TanStack Start generates routeTree.gen.ts from the files under src/routes. Create one router and one root-neutral Fig data store around that generated tree:

// src/router.tsx
import { createStartDataContext } from "@bgub/fig-tanstack-start";
import { createRouter } from "@bgub/fig-tanstack-router";
import { routeTree } from "./routeTree.gen.ts";

export function getRouter() {
  return createRouter({
    ...createStartDataContext(),
    isServer: typeof document === "undefined",
    routeTree,
  });
}

export type AppRouter = ReturnType<typeof getRouter>;

declare module "@tanstack/router-core" {
  interface Register {
    router: AppRouter;
  }
}

A route file exports the generated route's configuration and receives bound, fully typed hooks from that route:

// src/routes/users.$id.tsx
import { on } from "@bgub/fig-dom";
import { createFileRoute } from "@tanstack/solid-router";

export const Route = createFileRoute("/users/$id")({
  validateSearch: (search): { preview?: boolean } => ({
    preview: search.preview === true,
  }),
  loaderDeps: ({ search }) => ({ preview: search.preview === true }),
  component: User,
});

function User() {
  const { id } = Route.useParams();
  const { preview } = Route.useLoaderDeps();
  const navigate = Route.useNavigate();

  return (
    <article>
      <Route.Link to="/">Home</Route.Link>
      <button mix={on("click", () => navigate({ to: "/" }))} type="button">
        Done
      </button>
      <p>{preview ? `Previewing ${id}` : `User ${id}`}</p>
    </article>
  );
}

@tanstack/solid-router is currently a compiler-only compatibility ID. The Start plugin maps it directly to this package; no Solid Router adapter runtime is installed or bundled. TypeScript needs the equivalent path mapping:

{
  "compilerOptions": {
    "paths": {
      "@tanstack/solid-router": ["./node_modules/@bgub/fig-tanstack-router"]
    }
  }
}

The root route normally renders Router-managed document state and Start's Fig data snapshot before the bootstrap scripts:

import { StartScripts, type StartDataContext } from "@bgub/fig-tanstack-start";
import {
  createRootRouteWithContext,
  HeadContent,
  Outlet,
} from "@bgub/fig-tanstack-router";

export const Route = createRootRouteWithContext<StartDataContext>()({
  component: Document,
});

function Document() {
  return (
    <html lang="en">
      <head>
        <HeadContent />
      </head>
      <body>
        <Outlet />
        <StartScripts />
      </body>
    </html>
  );
}

Route stylesheets, preload hints, preconnects, font preloads, and async scripts are translated into Fig asset resources owned by the matched route. Title, meta, inline styles, JSON-LD, and synchronous scripts retain their document position. Configure manifest cross-origin behavior on the router so it is available before the root document renders:

const router = createRouter({
  assetCrossOrigin: { script: "anonymous", stylesheet: "use-credentials" },
  routeTree,
});

getRouteApi(routeId) provides a route-bound interface outside the route's own module. useMatches reads or selects the active match list; useMatchRoute and MatchRoute test locations reactively; Navigate performs declarative post-commit navigation. Selector hooks accept structuralSharing, falling back to the router's defaultStructuralSharing; location hooks accept selectors, loose hooks accept strict: false, and match, params, and search reads can return undefined with shouldThrow: false.

Provider and navigation lifecycle

RouterProvider can merge partial router options and route context into an existing router. Initial loaders see these values on their first run, and later provider renders preserve context fields they do not replace:

<RouterProvider context={{ session }} defaultPreload="intent" router={router} />

Browser navigation runs in a Fig transition, keeping the previously resolved match tree visible until loading settles. Router lifecycle subscriptions fire in commit order: onLoad, onBeforeRouteMount, onResolved, then onRendered. The provider skips a duplicate initial load during hydration, normalizes validated locations in browser history, and cleans up its history and transition bindings on unmount. A superseded navigation cannot publish a late resolved state.

Fig structural <ViewTransition> boundaries own route animations. The adapter suppresses Router Core's document wrapper, so a viewTransition navigation option cannot create a second nested browser transition.

Ordinary route changes do not use Activity: the previous tree is replaced after the transition. Retaining inactive route trees would require a separate keep-alive contract for their state, effects, and data ownership.

Navigation blocking

useBlocker supports TanStack's modern object contract without its deprecated positional overloads. Set withResolver: true to render explicit proceed/reset controls; useCanGoBack reacts to the current history index:

const blocker = useBlocker({
  shouldBlockFn: () => formIsDirty,
  withResolver: true,
});
const canGoBack = useCanGoBack();

return blocker.status === "blocked" ? (
  <nav>
    <button mix={on("click", blocker.proceed)}>Discard and leave</button>
    <button mix={on("click", blocker.reset)}>Stay</button>
  </nav>
) : (
  <button disabled={!canGoBack} mix={on("click", () => history.back())}>
    Back
  </button>
);

Route data: delegate keyed values to Fig

Fig data resources are the external cache for keyed route data — TanStack's "pass all loader events to an external cache" pattern. Router Core decides when loaders run; the Fig store owns value identity, deduplication, freshness, hydration, errors, and render-time reads.

createStartDataContext() places the Fig data handle at router.context.data. A blocking route loader calls ensureRouteData, while the component reads the same entry with readData:

import { dataResource, readData } from "@bgub/fig";
import { ensureRouteData } from "@bgub/fig-tanstack-router";
import { createFileRoute } from "@tanstack/solid-router";

const userResource = dataResource({
  key: (id: string) => ["user", id],
  load: async (id, { signal }) => fetchUser(id, signal),
});

export const Route = createFileRoute("/users/$id")({
  loader: ({ context, params }) =>
    ensureRouteData(context, userResource, params.id),
  component: User,
});

function User() {
  const { id } = Route.useParams();
  const user = readData(userResource, id);
  return <h1>{user.name}</h1>;
}

ensureRouteData deliberately resolves to void, so Router Core does not retain a second copy in loaderData. For non-blocking streaming, call context.data.preloadData(resource, ...args) and return; the component's readData suspends through Fig until the entry settles. Returning void lets the route commit its Suspense fallback independently and keeps resource values out of Router dehydration.

Loaders return void, and the adapter exposes no useLoaderData: a loader value would be a second cache with a second wire format (Router Core retains it per match; TanStack Start dehydrates it through its own transport). In dev builds, a match that commits with loaderData set while router.context.data is configured throws a diagnostic naming the route. Derive navigation-scoped values from useLoaderDeps, search params, or beforeLoad-returned route context; use a data resource when a value is keyed, shared, hydrated, independently invalidated, refreshed, or streamed. Routers created without context.data keep Router Core's native loader semantics untouched.

Support policy

See TanStack Router compatibility for the feature-level matrix and Fig equivalents for intentionally omitted adapter conveniences.

| Tier | Contract | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Guaranteed | Generated file and lazy routes; typed route APIs and structural-sharing selectors; Router creation/provider; native links with active/inactive props and render-function children; navigation blocking and back-history state; loaders, redirects, route masks, not-found and route errors; pending timing and remount dependencies; route-level Start SSR policies/hydration; scroll restoration; head and script output; search/history helpers; Fig data-resource delegation. | | Compatibility | createRootRoute and createRoute for code-created route trees. These use the same Router Core machinery but are not the recommended Start authoring path. | | Deferred | useElementScrollRestoration, parent/child match selectors, custom-link construction through useLinkProps or createLink, and proximity preloading. | | Deliberately omitted | Additional deprecated compatibility classes and aliases; public Await, ClientOnly, CatchBoundary, and ScrollRestoration clones; Activity-based keep-alive routing; useLoaderData (the Fig data store is the single route-data cache — read with readData). Fig primitives or internal adapter behavior cover these concerns. |

The adapter is pinned and tested against @tanstack/[email protected]. Upgrades are conformance changes: generated-route, navigation, SSR, data, and document tests must pass against the new version before the pin moves.

Native link contract

Link renders a native anchor. It intercepts only unmodified primary clicks; downloads, external URLs, modifier keys, and non-_self targets retain native browser behavior. Disabled links omit href and expose aria-disabled. Preloading supports intent, render, and viewport, and active links expose aria-current="page" plus data-status="active". activeProps and inactiveProps compose native anchor state, while render-function children receive isActive and isTransitioning. linkOptions and createRouteMask provide reusable typed options without runtime wrappers. Unsupported preloadIntentProximity is rejected by LinkProps rather than silently ignored.

Code-created route trees

Standalone applications may still assemble a tree with createRootRoute, createRoute, and route.addChildren. This compatibility surface remains tested because it is useful for small routers and focused adapter tests. New TanStack Start applications should use generated file routes so the generator can provide route typing and automatic code splitting.

Construct routers and standalone route APIs with createRouter and getRouteApi; their concrete classes are adapter internals.