One of the first decisions when starting a new TanStack Start app is how to organise the source tree. The framework gives you file-based routing conventions for src/routes/, but the rest of src/ is up to you. After building a few apps with it, this is the structure I've settled on.
src/
├── components # Reusable React components
├── hooks # Custom React hooks
├── lib # Utility functions, DB code and business logic
├── routes/
│ ├── posts/
│ │ ├── route.tsx # Directory route for /posts; parent layout for child routes
│ │ ├── index.tsx # Index route for /posts
│ │ └── $slug.tsx # Dynamic route for /posts/:slug
│ ├── _account/
│ │ ├── route.tsx # Pathless layout route; wraps children without adding /account
│ │ ├── orders.tsx # Route for /orders
│ │ └── profile.tsx # Route for /profile
│ ├── __root.tsx # Root route; wraps the entire app
│ ├── index.tsx # Index route for /
│ ├── about.tsx # Route for /about
│ ├── robots[.]txt.ts # File route for /robots.txt
│ └── $.tsx # Catch-all / splat route
├── global.css # Global styles imported by __root.tsx
├── router.tsx # Router instance and app router setup
└── routeTree.gen.ts # Generated route tree; do not edit manuallysrc/routes/
The entire routing layer lives here and TanStack Router generates routeTree.gen.ts from it automatically. The file naming conventions carry a lot of meaning, so it's worth understanding each one before adding routes.
__root.tsx
This is the root of the route tree. Every other route renders inside the outlet defined here, so it's the right place for things that wrap the whole app like providers, analytics, global navigation, and the root <html> shell when doing SSR. I also import global.css here.
Directory routes
Nesting routes under a directory creates a parent/child relationship in the URL tree. A posts/route.tsx file becomes the layout wrapper for everything under /posts, and posts/index.tsx renders at /posts itself.
routes/
└── posts/
├── route.tsx # Layout rendered for /posts and /posts/* — put shared UI here
├── index.tsx # Renders at /posts
└── $slug.tsx # Renders at /posts/some-slugPathless layout routes
The underscore prefix (_account/) creates a layout group that shares a route.tsx wrapper without contributing a URL segment. Routes inside _account/ render at /orders and /profile, not /account/orders and /account/profile.
Dynamic routes
$slug.tsx creates a dynamic segment. The filename becomes the param name, so $slug.tsx gives you params.slug in the loader and component. You can have multiple dynamic segments in a path by nesting directories.
Special file routes
Files with dots in the name need escaped brackets, for example robots[.]txt.ts maps to /robots.txt. These are server routes, which export a createServerFileRoute handler rather than the standard createFileRoute.
$.tsx
The catch-all splat route. Any URL that doesn't match another route falls through to this one. On a statically deployed site I use it to render a 404 page, since the static host won't have a server to return a 404 status code for unknown paths.
Continue reading
Mar 2026
·4 min read
Mar 2026
·5 min read