--- id: frontend-tanstack-start title: TanStack Start β€” modern fullstack React category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [frontend, fullstack, vibe-coding] tech_stack: { language: "TS / React", applicable_to: ["Frontend"] } applied_in: [] aliases: [TanStack Start, TanStack Router, server functions, Remix alternative, fullstack React] --- # TanStack Start > Next.js alternative. **TanStack Router (file-based) + Vite + server functions**. Full-stack React, type-safe end-to-end. ## πŸ“– 핡심 κ°œλ… - File-based routing (Next λΉ„μŠ·). - Type-safe route params (search, path). - Server functions (RPC 식). - Vite κ°€ build (Next 보닀 simple). ## πŸ’» μ½”λ“œ νŒ¨ν„΄ ### Setup ```bash npm create @tanstack/start@latest my-app cd my-app npm run dev ``` ### File-based route ``` src/routes/ β”œβ”€β”€ __root.tsx # layout β”œβ”€β”€ index.tsx # / β”œβ”€β”€ about.tsx # /about β”œβ”€β”€ posts/ β”‚ β”œβ”€β”€ index.tsx # /posts β”‚ └── $id.tsx # /posts/:id └── _authenticated/ # auth-required β”œβ”€β”€ dashboard.tsx ``` ### Route μ •μ˜ ```tsx // src/routes/posts/$id.tsx import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/posts/$id')({ component: PostPage, loader: ({ params }) => fetchPost(params.id), }); function PostPage() { const post = Route.useLoaderData(); const params = Route.useParams(); return

{post.title}

; } ``` β†’ Type-safe params (string `id`). ### Search params (type-safe) ```tsx import { z } from 'zod'; export const Route = createFileRoute('/products')({ validateSearch: z.object({ page: z.number().default(1), sort: z.enum(['asc', 'desc']).default('asc'), }), loader: ({ deps }) => fetchProducts(deps), loaderDeps: ({ search }) => search, component: Products, }); function Products() { const { page, sort } = Route.useSearch(); return
Page {page}
; } ``` β†’ URL `?page=1&sort=asc` κ°€ type-safe. ### Server function (RPC) ```tsx // src/routes/posts/$id.tsx import { createServerFn } from '@tanstack/start'; export const getPost = createServerFn('GET', async (id: string) => { return await db.posts.findUnique({ where: { id } }); }); // Client λ˜λŠ” server κ°€ 호좜 const post = await getPost('abc'); ``` β†’ `getPost` κ°€ server-only β€” client bundle μ•ˆ 듀어감. ### Mutation (server function) ```tsx export const createPost = createServerFn('POST', async (data: PostInput) => { // Server-only const session = useSession(); if (!session) throw new Error('unauthorized'); return db.posts.create({ data }); }); // Client async function handleSubmit(data: PostInput) { const post = await createPost(data); } ``` ### Loader + suspense ```tsx export const Route = createFileRoute('/dashboard')({ loader: async () => { const [user, stats] = await Promise.all([ fetchUser(), fetchStats(), ]); return { user, stats }; }, pendingComponent: () => , errorComponent: ({ error }) => , }); ``` β†’ Suspense / error boundary κ°€ declarative. ### Defer (streaming) ```tsx export const Route = createFileRoute('/dashboard')({ loader: async () => { const user = await fetchUser(); // 빠름 β€” wait const slow = fetchSlow(); // promise β€” defer return { user, slow }; }, }); function Dashboard() { const { user, slow } = Route.useLoaderData(); return ( <>

{user.name}

}> ); } ``` β†’ User λΉ λ₯Έ first paint, slow κ°€ streaming. ### Layout (nested) ```tsx // src/routes/__root.tsx import { Outlet } from '@tanstack/react-router'; export const Route = createRootRoute({ component: () => ( <>