Skip to content
Dashboard

Choosing between TanStack Start and Next.js

Copy link to headingEach framework picks a different starting point

Copy link to headingRouting and type safety diverge on the URL itself

app/orders/page.tsx
type OrdersPageProps = {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
export default async function OrdersPage({ searchParams }: OrdersPageProps) {
const params = await searchParams
// Simplified for illustration; production code should also guard against string[] cases.
const status = (params.status as string) ?? 'open'
const page = Number(params.page ?? 1)
const sort = (params.sort as 'newest' | 'oldest') ?? 'newest'
const orders = await fetchOrders({ status, page, sort })
return <OrdersTable orders={orders} />
}

src/routes/orders.tsx
// src/routes/orders.tsx
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
const ordersSearchSchema = z.object({
status: z.enum(['open', 'closed', 'all']).default('open'),
page: z.number().int().min(1).default(1),
sort: z.enum(['newest', 'oldest']).default('newest'),
})
export const Route = createFileRoute('/orders')({
validateSearch: ordersSearchSchema,
loaderDeps: ({ search: { status, page, sort } }) => ({ status, page, sort }),
loader: ({ deps }) => fetchOrders(deps),
component: OrdersPage,
})
function OrdersPage() {
const search = Route.useSearch()
const orders = Route.useLoaderData()
return <OrdersTable filters={search} orders={orders} />
}

Copy link to headingData loading runs on different rails

Copy link to headingBuild and dev loop: Vite vs Turbopack

Copy link to headingComposability versus integration is a values choice

Copy link to headingThe shape each framework fits best

Copy link to headingMaturity scales with adoption, not architecture

Copy link to headingDeployment on Vercel

Copy link to headingA decision checklist for your next project

Copy link to headingFrequently asked questions

Ready to deploy?