Capítulo 4 de 24

Chapter 4: Basic Routing (Code-Based)

Core Idea

The code-based twin of the basic file-based example: same posts list, dynamic $postId route, and nested pathless layouts, but built with createRoute/addChildren, plus it demonstrates splitting a route's component into a separate lazily-loaded file with .lazy() and createLazyRoute, code-based routing's answer to file-based route code splitting.

Setup

  • Routing style: code-based, with manual lazy-loading via .lazy(() => import(...))
  • Key dependencies: @tanstack/react-router, @tanstack/react-router-devtools, redaxios. No @tanstack/router-plugin since there's no codegen.
  • Structure: src/main.tsx defines rootRoute, indexRoute, a postsLayoutRoute (loader only, component deferred), a pathlessLayoutRoute with nested pathlessLayoutARoute/pathlessLayoutBRoute, then assembles everything with addChildren. src/posts.lazy.tsx holds the actual PostsLayoutComponent UI, loaded on demand. src/posts.ts holds the plain data-fetching functions (fetchPosts, fetchPost).

Code Example

// src/main.tsx
export const postsLayoutRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: 'posts',
  loader: () => fetchPosts(),
}).lazy(() => import('./posts.lazy').then((d) => d.Route))

const postRoute = createRoute({
  getParentRoute: () => postsLayoutRoute,
  path: '$postId',
  errorComponent: PostErrorComponent,
  loader: ({ params }) => fetchPost(params.postId),
  component: PostComponent,
})
// src/posts.lazy.tsx
export const Route = createLazyRoute('/posts')({
  component: PostsLayoutComponent,
})

function PostsLayoutComponent() {
  const posts = Route.useLoaderData()
  return (
    <div className="p-2 flex gap-2">
      <ul className="list-disc pl-4">
        {posts.map((post) => (
          <li key={post.id}>
            <Link to="/posts/$postId" params={{ postId: post.id }}>
              {post.title.substring(0, 20)}
            </Link>
          </li>
        ))}
      </ul>
      <Outlet />
    </div>
  )
}
  • What it demonstrates: .lazy(() => import(...)) splits a route's component into its own JS chunk while loader, path, and getParentRoute stay in the eagerly-loaded route tree; createLazyRoute('/posts') in the split file must reference the same path string to reattach to the parent route definition.

Key Takeaways

  1. In code-based routing, .lazy() is the direct equivalent of file-based routing's automatic .lazy.tsx splitting: keep data/loader logic in the main bundle, defer UI code.
  2. Link to="/posts/$postId" params={{ postId: post.id }} gives compile-time checking that the param object matches the route's declared dynamic segments.
  3. Pathless layout nesting (_pathlessLayout -> _nestedPathlessLayout -> route-a/route-b) works identically to file-based routing; only the syntax for declaring the nesting differs (id: '_pathlessLayout' plus addChildren versus filenames).

Connects To

  • ch003-basic-file-based: same app and routes, generated from src/routes/*.tsx files instead of hand-assembled with addChildren.
  • ch002-quickstart: the minimal version of this same code-based createRoute/addChildren pattern, without loaders or lazy splitting.