Skip to content
Dashboard

Choosing between TanStack Start and Next.js

Link to headingEach framework picks a different starting point

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} />
}

Link to headingData loading runs on different rails

Link to headingBuild and dev loop: Vite vs Turbopack

Link to headingComposability versus integration is a values choice

Link to headingThe shape each framework fits best

Link to headingMaturity scales with adoption, not architecture

Link to headingDeployment on Vercel

Link to headingA decision checklist for your next project

Link to headingFrequently asked questions

Ready to deploy?