Route Trees, Layouts, and Outlets
Purpose and Scope
This page explains how TanStack Router turns a set of route definitions into a route tree, and how that tree becomes nested UI through layout routes and outlets. A route tree is the structural contract for the application: it describes parent-child relationships, path segments, pathless layout boundaries, and the components that should render for each match. When a user navigates, the router matches the current location against that tree and renders the matched branch from parent to child. Understanding that branch is essential before working with loaders, context, errors, or typed navigation because those features are all attached to routes in the same hierarchy.
Sources: docs/router/guide/creating-a-router.md, docs/router/guide/outlets.md, docs/router/api/router/RouteOptionsType.md
TanStack Router supports both code-based and file-based route trees. In code-based routing, you build the tree manually, commonly by starting with a root route and calling its addChildren method. In file-based routing, route files are discovered from a configured routes directory and a generated routeTree.gen.ts file exports the tree for the application to import. Both approaches converge on the same runtime requirement: createRouter needs a routeTree option so the router can manage matching, rendering, navigation transitions, and router-wide configuration.
Sources: docs/router/guide/creating-a-router.md, docs/router/api/file-based-routing.md
Relevant Source Files
docs/router/guide/outlets.md- Defines nested rendering, theOutletcomponent, the default outlet behavior when a route component is omitted, and framework examples for React and Solid root route layouts.docs/router/guide/creating-a-router.md- Explains thatcreateRoutercreates the router instance, thatrouteTreeis required, and that the route tree can come from generated file-based routing or manual code-based routing.docs/router/api/file-based-routing.md- Documents file-based routing configuration, includingroutesDirectory,generatedRouteTree, and therouteTokenconvention used to identify layout route files in the route directory.docs/router/api/router/RouteOptionsType.md- Describes route-level options such asgetParentRoute,path,id, andcomponent, including the rule thatcomponentdefaults to<Outlet />.
Route Tree as the Application Contract
The route tree is more than a list of URLs. It is the shape that connects paths, params, search schemas, loaders, route context, components, and navigation APIs. The official Router positioning describes the route tree as the application contract because the same tree is used to generate typed APIs for links, navigation, URL state, loader data, pending states, and code splitting. At the implementation level reflected in the docs, the router instance is responsible for managing that tree, matching routes, and coordinating route transitions after you pass the tree into createRouter.
Sources: docs/router/guide/creating-a-router.md
A parent route represents a boundary in that contract. It can contribute a path segment, a component, error and pending UI, validated search state, params parsing, loader behavior, or context for descendants. A child route declares its parent with getParentRoute in route options, which is required to provide full type safety to child route configurations and to ensure the tree is built correctly. This is why nested routing is not only visual nesting; the parent route establishes the type and runtime environment inherited by its children.
Sources: docs/router/api/router/RouteOptionsType.md
The root route is the top-level parent for the application. The creating-a-router guide shows that a file-based app usually imports routeTree from ./routeTree.gen, while a code-based app can create the tree manually from the root route. Either way, the root route is commonly where app-wide shell UI belongs: providers, top-level chrome, global error handling, or a title. The root component can then render an outlet to leave a placeholder for whichever top-level child route matches the current location.
Sources: docs/router/guide/creating-a-router.md, docs/router/guide/outlets.md
Layout Routes and Pathless Layouts
A layout route is a route whose main job is to wrap child routes with shared UI. In file-based routing, the API reference documents routeToken as the convention used to identify layout route files in the route directory. Its default value is route, and the docs note that src/routes/posts.tsx, src/routes/posts.route.tsx, and src/routes/posts/route.tsx all map to the same runtime URL, /posts. That convention lets teams choose whether a layout lives beside siblings or inside a route folder while preserving the same URL shape.
Sources: docs/router/api/file-based-routing.md
Route options also support pathless layout routes. A normal route usually provides a path, which is the segment used to match the route. If no path is provided, the route needs an id instead. The API reference describes this id as the unique identifier for a pathless layout route and states that such a route does not match against the location pathname; its routes are flattened into the parent route for matching. Practically, this means you can insert UI and logical boundaries without inserting another URL segment.
Sources: docs/router/api/router/RouteOptionsType.md
Use a pathful layout when the shared UI corresponds to a visible URL segment, such as /posts or /dashboard. Use a pathless layout when the shared UI is an application structure rather than a path structure, such as an authenticated frame, a marketing shell, or a route group that should not appear in the URL. In both cases, the layout’s component decides where children appear by rendering an outlet. The difference is whether the layout contributes to pathname matching or only to the route hierarchy.
Sources: docs/router/guide/outlets.md, docs/router/api/router/RouteOptionsType.md
Outlets and Nested Rendering
An outlet is the rendering placeholder for the next matching child route. The outlets guide defines <Outlet /> as a component that can be rendered anywhere inside a route component tree and does not take props. When a route has a matching child, the child route’s component renders at the outlet position. When there is no matching child, the outlet renders null. This behavior gives parent routes precise control over layout composition: a parent can render headers, sidebars, tabs, or wrappers before, around, or after its child content.
Sources: docs/router/guide/outlets.md
The most common example is the root route. The guide shows a root route component that renders a heading and then an <Outlet />, allowing top-level routes to render below the application title. The same pattern scales to deeper layouts: a dashboard route might render navigation and an outlet, while a nested invoice route renders invoice details inside that dashboard frame. The route tree determines which branch is active, and outlets determine where each child in that active branch appears in the DOM or component tree.
Sources: docs/router/guide/outlets.md
A route component is optional. The RouteOptions API states that the component property defaults to <Outlet />, and the outlets guide calls out the same behavior as a tip. This default is important for structural routes that only need to connect children, provide route options, or participate in typing without adding visible UI. If you omit a component from a parent route, the router still has a place to render matched descendants. If you provide a component, you become responsible for placing the outlet where child content should appear.
Sources: docs/router/guide/outlets.md, docs/router/api/router/RouteOptionsType.md
Code-Based and File-Based Composition
In a code-based route tree, parent-child composition is explicit in TypeScript. A route points to its parent with getParentRoute, provides either a path or an id, and may define a component. The root route then gathers children with addChildren to create the final routeTree. This approach is useful when a team wants all route relationships expressed in code, or when routes are generated from application configuration rather than from the filesystem. The same outlet rules apply because the runtime tree is still nested.
Sources: docs/router/guide/creating-a-router.md, docs/router/api/router/RouteOptionsType.md
const routeTree = rootRoute.addChildren([
// child routes here
])In a file-based route tree, the plugin or generator writes the tree to the configured generatedRouteTree path, which defaults to ./src/routeTree.gen.ts. The routes are discovered from routesDirectory, which defaults to ./src/routes. File naming conventions and options such as routeToken decide how route files become path routes or layout routes. Once generated, the application imports routeTree and passes it to createRouter, so the rendering and matching model remains the same as a manually assembled tree.
Sources: docs/router/guide/creating-a-router.md, docs/router/api/file-based-routing.md
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({
routeTree,
})The main operational difference is ownership. In code-based routing, developers maintain the tree directly. In file-based routing, developers maintain route files and configuration, while the generated tree keeps the route files connected to router APIs. The official docs emphasize this generated contract: route files, params, search schemas, and loader outputs stay connected to links, navigation, and data access. For layout and outlet work, that means moving a route file or changing a layout filename changes the generated parent-child contract, not just the file location.
Sources: docs/router/api/file-based-routing.md, docs/router/guide/creating-a-router.md
System-to-Code Mapping
| Concept | Source-backed contract | Practical effect |
|---|---|---|
| Router instance | createRouter creates the router and receives routeTree | Central place for matching, rendering coordination, transitions, and router-wide options |
| Route tree | Generated from files or built with rootRoute.addChildren | Defines the parent-child branch that matches each location |
| Parent route | Child routes use getParentRoute | Preserves hierarchy and type safety for descendant routes |
| Path route | Route option provides path | Contributes a segment to URL matching |
| Pathless layout | Route option provides id without path | Adds hierarchy or UI without adding a URL segment |
| Outlet | Route component renders <Outlet /> | Marks where the next matching child route renders |
| Omitted component | component defaults to <Outlet /> | Lets structural routes pass rendering through to descendants |
Implementation Details and Design Constraints
The most important design constraint is that nesting has two dimensions: URL matching and UI rendering. A child route can only render when its branch matches, but where it renders depends on the parent component’s outlet placement. This separation lets you build predictable layouts. For example, /posts can be both a URL segment and a shared posts layout, while a pathless authenticated route can wrap many protected pages without changing their URLs. The route options API gives these two cases separate representations through path and id.
Sources: docs/router/api/router/RouteOptionsType.md, docs/router/guide/outlets.md
Another useful constraint is that parents must be explicit for type safety. getParentRoute is required in route options, not just inferred from a string path, because the parent route carries the types that descendants depend on. Those types can include route params, search schemas, loader output, and context in broader Router usage. Even when a page is focused only on layouts, this explains why route tree composition matters: a visual shell is also a typed boundary, and moving children between parents can change the APIs available to those children.
Sources: docs/router/api/router/RouteOptionsType.md
For file-based teams, treat the generated route tree as build output and the route directory as the source of truth. Configure routesDirectory and generatedRouteTree deliberately, and avoid naming options that conflict with file naming conventions. The file-based routing API warns that setting prefix or ignore options to match convention tokens can cause unexpected behavior. Layout routes are especially sensitive to naming, because the routeToken determines which files are interpreted as layout route files rather than ordinary route modules.
Sources: docs/router/api/file-based-routing.md
Example: Root Layout with Child Outlet
The simplest layout is a root route that renders app chrome and an outlet. The example below mirrors the documented pattern: create a root route with a component, render stable UI, and place <Outlet /> where matched children should appear. The same idea applies to any nested route. If the component is omitted, the route behaves as a pass-through outlet by default; if the component is present and no outlet is rendered, matched children have no documented outlet location in that component.
Sources: docs/router/guide/outlets.md, docs/router/api/router/RouteOptionsType.md
import { createRootRoute, Outlet } from '@tanstack/react-router'
export const Route = createRootRoute({
component: RootComponent,
})
function RootComponent() {
return (
<div>
<h1>My App</h1>
<Outlet />
</div>
)
}Next Steps
When designing a route tree, start by sketching the user-visible URL hierarchy and then mark which nodes need shared UI. Convert URL-bearing nodes into path routes, convert non-URL shells into pathless layout routes, and place outlets exactly where child screens should render. If you use file-based routing, confirm the route files generate the intended tree in src/routeTree.gen.ts. If you use code-based routing, review each child’s getParentRoute and the root route’s addChildren call. Then continue to pages on file-based routing, type safety, data loading, and authenticated routes to see how the same tree carries more application behavior.
Sources: docs/router/guide/creating-a-router.md, docs/router/api/file-based-routing.md