Capítulo 23 de 24

Chapter 23: Monorepo Setup with Lazy Loading

Core Idea

This example uses the same router/app package split as router-monorepo-simple, but tunes the router for lazy-loaded, code-split route components: router-monorepo-simple's own main.tsx comment notes "Not lazy loaded for simplicity, but you could expose from your library component individually...so that you have code splitting", and this example is that follow-through, pairing a code-split component wiring in the app package with defaultPreload: 'intent' in the router.

Setup

  • Routing style: file-based route tree in the router package (same as ch022, createFileRoute compiled into routeTree.gen.ts), route components attached from the app package
  • Key dependencies: same as router-monorepo-simple, @tanstack/react-router, @tanstack/router-plugin, @tanstack/history, redaxios, zod, under the @router-mono-simple-lazy/* package scope
  • Structure: packages/router/src/router.tsx and packages/router/src/routes/$postId.ts are structurally identical to the ch022 baseline (same loader-only route, same RouterIds export), the router-level difference observed is defaultPreload: 'intent' added to createRouter. The lazy-component wiring itself lives in the app package's component map (not included in the fetched source set for this example).

Code Example

// packages/router/src/router.tsx
export const router = createRouter({
  routeTree,
  defaultPreload: 'intent',
  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 (unchanged from the baseline)
export const Route = createFileRoute('/$postId')({
  loader: ({ params }) => fetchPost(params.postId),
})
  • What it demonstrates: defaultPreload: 'intent' at the router level, which becomes important once the app package's components are code-split (e.g. via React.lazy), since it starts fetching a route's chunk and data on hover/focus instead of waiting for navigation.

Key Takeaways

  1. defaultPreload: 'intent' is cheap to add to any router and pairs naturally with lazy-loaded route components, it hides the code-split chunk's network latency behind the user's hover/focus intent.
  2. The router/route-tree package (router) doesn't need to know whether the components attached to it later are lazy or eager, the split from ch022 (loader-only routes, component attached externally via route.update()) already decouples that decision to the app package.
  3. When comparing packages across a monorepo progression like this one, check createRouter options and package.json scope names first, they are often the only lines that differ between otherwise-identical route files.

Connects To

  • router-monorepo-simple (ch022): the baseline this example builds on; the router/routes files are line-for-line the same except for defaultPreload: 'intent'.
  • router-monorepo-react-query (ch024): the next step in the same progression, adding a dedicated post-query package for TanStack Query options instead of (or alongside) plain loaders.