Capítulo 22 de 24
This example splits a TanStack Router application across a pnpm workspace: a router package owns the router instance, route tree, and route definitions (without components), while an app package supplies the actual React components and attaches them to the routes at startup via route.update(). This lets a router/route-tree "shape" be versioned and shared independently of its UI implementation.
router package (routes/$postId.ts uses createFileRoute, compiled into routeTree.gen.ts by @tanstack/router-plugin); routes are defined without a component, which is attached later from the app package.@tanstack/react-router, @tanstack/router-plugin, @tanstack/history, redaxios, zod (in router); @router-mono-simple/router and @router-mono-simple/post-feature as workspace:* dependencies (in app)packages/router/src/router.tsx calls createRouter and re-exports RouterIds for type-safe route id lookups. packages/router/src/routes/$postId.ts defines a route with only a loader, no component. packages/app/src/main.tsx imports the router and a separate post-feature package's components, then wires them onto the route tree at runtime.// packages/router/src/router.tsx
export const router = createRouter({
routeTree,
defaultPendingComponent: () => <div>Loading form global pending component...</div>,
scrollRestoration: true,
})
export type RouterType = typeof router
export type RouterIds = RouteIds<RouterType['routeTree']>
// packages/router/src/routes/$postId.ts
export const Route = createFileRoute('/$postId')({
loader: ({ params }) => fetchPost(params.postId),
})
// packages/app/src/main.tsx
const routerMap = {
'/': PostsListComponent,
'/$postId': PostIdComponent,
__root__: RootComponent,
} as const satisfies Record<RouterIds, (() => React.ReactElement) | null>
Object.entries(routerMap).forEach(([path, component]) => {
const foundRoute = router.routesById[path as RouterIds]
foundRoute.update({ component: component ?? EmptyComponent })
})
component/errorComponent to routes after the fact with route.update(), keyed by RouterIds (a type derived from the route tree), so the router package stays UI-free and the app package supplies presentation.RouteIds<RouterType['routeTree']> gives a compile-time-checked map of every route id, so routerMap in the consuming app is exhaustive: forgetting a route is a type error.loaders in the router package but components in the app package lets multiple apps reuse the same data-fetching/route-tree package with different UIs.route.update({ component, errorComponent }) is called once at app bootstrap, before RouterProvider renders, this is a one-time wiring step, not a per-render pattern.workspace:* dependencies (@router-mono-simple/router, @router-mono-simple/post-feature), each with its own package.json and build step (vite build && tsc --noEmit).post-query package that owns TanStack Query options, consumed by the router package's loaders.