If you already know TanStack Router and you're picking up Next.js App Router, most of routing is just a vocabulary swap. This post is the cheat sheet I wish I had — a straight mapping between the two, plus the handful of things that actually matter once you go beyond routing basics.
The Core Mapping
| Concept | TanStack Start | Next.js App Router |
|---|---|---|
| Routes directory | src/routes/ |
app/ |
| Home | src/routes/index.tsx |
app/page.tsx |
Static route (/about) |
src/routes/about.tsx |
app/about/page.tsx |
| Nested route | src/routes/dashboard/settings.tsx |
app/dashboard/settings/page.tsx |
| Dynamic route | $postId.tsx |
[postId]/page.tsx |
| Root layout | __root.tsx |
app/layout.tsx |
| Outlet for children | <Outlet /> |
{children} |
| Loading UI | pendingComponent |
loading.tsx |
| Error UI | errorComponent |
error.tsx |
| Not found |
notFoundComponent / notFound()
|
not-found.tsx / notFound()
|
| Route params | Route.useParams() |
useParams() or server params
|
| Search params | Route.useSearch() |
useSearchParams() |
| Link | <Link to="/about" /> |
<Link href="/about" /> |
| Programmatic nav | useNavigate() |
useRouter() |
| Redirect | redirect() |
redirect() |
| Route guards | beforeLoad |
middleware / server checks |
| Data loading | loader |
Server Components / fetch
|
| Server functions | Start server functions | Server Actions / Route Handlers |
| Type-safe routing | Built-in, excellent | Weaker, no first-class typing |
| Search param validation | Built into router | Usually a library (e.g. Zod) |
Folder Structure, Side by Side
TanStack Start:
src/
└── routes/
├── __root.tsx
├── index.tsx
├── about.tsx
└── dashboard/
├── route.tsx
├── index.tsx
└── settings.tsx
Next.js:
app/
├── layout.tsx
├── page.tsx
├── about/
│ └── page.tsx
└── dashboard/
├── layout.tsx
├── page.tsx
└── settings/
└── page.tsx
Quick translation table for the folder structure:
__root.tsx → layout.tsx
index.tsx → page.tsx
Outlet → children
$slug.tsx → [slug]/page.tsx
loader → Server Component data fetching
beforeLoad → middleware / server checks
<Link> → <Link>
useNavigate() → useRouter()
notFound() → notFound()
pendingComponent → loading.tsx
errorComponent → error.tsx
Same Route, Two Mental Models
TanStack Router — you define a route object with lifecycle hooks:
export const Route = createFileRoute('/products/$id')({
loader: ({ params }) => getProduct(params.id),
component: ProductPage,
})
Next.js App Router — the filesystem is the route tree, and the component itself fetches data:
// app/products/[id]/page.tsx
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const product = await getProduct(id)
return <Product product={product} />
}
That's the real mental shift: TanStack Router thinks in router + route objects. Next.js thinks in filesystem + Server Components.
If you're coming from TanStack Start, spend your energy on that list — routing itself you already basically know.
Top comments (0)