0% found this document useful (0 votes)
4 views11 pages

Tanstack Start Tutorial

TanStack Start is a full-stack React framework that combines file-based routing, server-side rendering, and server functions, similar to Next.js but utilizing TanStack's ecosystem. The tutorial covers installation, project structure, routing mechanics, data fetching, and creating API endpoints, making it beginner-friendly. It also highlights features like type-safe navigation, loaders for data fetching, and server functions that allow backend logic to be executed directly within React components.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views11 pages

Tanstack Start Tutorial

TanStack Start is a full-stack React framework that combines file-based routing, server-side rendering, and server functions, similar to Next.js but utilizing TanStack's ecosystem. The tutorial covers installation, project structure, routing mechanics, data fetching, and creating API endpoints, making it beginner-friendly. It also highlights features like type-safe navigation, loaders for data fetching, and server functions that allow backend logic to be executed directly within React components.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

TanStack Start Tutorial for Complete Beginners

TanStack Start is a full-stack React framework built on top of TanStack Router. Think of
it like [Link], but powered by TanStack’s router/query ecosystem — you get file-based
routing, server-side rendering, and the ability to write server-only functions right next to
your components, without running a separate backend.

0. What You Need to Know First

Concept What it means

TanStack
The routing engine (file-based, fully type-safe)
Router

TanStack A framework wrapped around the Router that adds SSR, server functions,
Start and API routes

Vite The build tool Start uses under the hood

If you know React already, TanStack Start feels like: React + file-based routing + “server
functions” (like a mini backend) all in one project.

1. Install & Setup

Requirement
[Link] installed (check with node -v )

Fastest way — scaffold with the CLI

npx @tanstack/cli@latest create my-app

This walks you through prompts:

Project name
Package manager (npm/pnpm/yarn/bun)

Add-ons (TanStack Query, Tailwind, Clerk auth, Drizzle ORM, etc. — skip these for now
as a beginner)

Then:

cd my-app
npm install
npm run dev

Visit [Link]

You can also do --add-ons tailwind etc. directly as flags if you already know what you
want.

Manual setup (good for understanding what’s happening)

mkdir my-app && cd my-app


npm init -y
npm i @tanstack/react-start @tanstack/react-router react react-dom
npm i -D vite @vitejs/plugin-react typescript @types/react @types/react-dom

Then create [Link] , [Link] , and a src/routes folder — but honestly,


use the CLI for your first project. Manual setup is for later once you understand the pieces.

2. Project Structure

my-app/
├── src/
│ ├── routes/ ← THIS is where routing magic happens
│ │ ├── __root.tsx ← the root layout (always renders)
│ │ ├── [Link] ← "/"
│ │ ├── [Link] ← "/about"
│ │ └── posts/
│ │ ├── [Link] ← "/posts"
│ │ └── $[Link] ← "/posts/:postId" (dynamic)
│ ├── [Link] ← router configuration
│ └── [Link] ← AUTO-GENERATED, don't edit this
├── [Link]
└── [Link]
Key idea: the file/folder structure under src/routes = your URL structure. This is exactly
like [Link]’s app/ folder, but the config lives in [Link] and route generation happens
through a Vite plugin.

3. How Routing Works (Step by Step)

The Root Route — __root.tsx

Every app needs this. It wraps all pages — like a global layout ( <html> , <body> , nav bar,
etc.)

// src/routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'

export const Route = createRootRoute({


component: () => (
<html>
<head></head>
<body>
<nav>
<a href="/">Home</a> | <a href="/about">About</a>
</nav>
<Outlet /> {/* child route renders here */}
</body>
</html>
),
})

<Outlet /> is where the matched child page gets injected — same concept as {children}
in [Link] layouts.

A Basic Page — [Link] (matches / )

// src/routes/[Link]
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/')({


component: HomePage,
})

function HomePage() {
return <h1>Welcome Home!</h1>
}

Another Page — [Link] (matches /about )

// src/routes/[Link]
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/about')({


component: () => <h1>About Us</h1>,
})

Dynamic Routes — $[Link] (matches /posts/123 )


The $ prefix means “this is a URL parameter” (equivalent to [id] in [Link], or :id in
Express).

// src/routes/posts/$[Link]
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({


component: PostPage,
})

function PostPage() {
const { postId } = [Link]()
return <h1>Viewing Post #{postId}</h1>
}

Nested / Layout Routes


A file named the same as a folder (e.g. [Link] next to a posts/ folder) becomes a
layout for everything inside that folder:

routes/
├── [Link] ← layout wrapper for all /posts/* routes
└── posts/
├── [Link] ← /posts
└── $[Link] ← /posts/:postId

// [Link]
import { createFileRoute, Outlet } from '@tanstack/react-router'

export const Route = createFileRoute('/posts')({


component: () => (
<div>
<h2>Posts Section</h2>
<Outlet /> {/* [Link] or $[Link] renders here */}
</div>
),
})

Pathless / Group Layouts


A folder or file prefixed with _ doesn’t add a URL segment — it’s just for grouping/sharing
layout without affecting the path (similar to [Link]’s (group) folders).

404 / Not Found Route

// src/routes/__root.tsx
export const Route = createRootRoute({
component: RootComponent,
notFoundComponent: () => <p>Page not found!</p>,
})

4. Reading Query Params & Search Params

// src/routes/[Link]
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/search')({


component: SearchPage,
validateSearch: (search: Record<string, unknown>) => ({
q: (search.q as string) || '',
page: Number([Link]) || 1,
}),
})

function SearchPage() {
const { q, page } = [Link]()
return <p>Searching "{q}", page {page}</p>
}

Visiting /search?q=laptop&page=2 → q="laptop", page=2 . TanStack Router validates and


type-checks your search params — a big upgrade over plain URLSearchParams .
Linking Between Pages (type-safe navigation)

import { Link } from '@tanstack/react-router'

<Link to="/posts/$postId" params={{ postId: '5' }}>


Go to Post 5
</Link>

<Link to="/search" search={{ q: 'shoes', page: 1 }}>


Search Shoes
</Link>

If you typo a route name or forget a required param, TypeScript will catch it at compile
time.

5. Loaders — Fetching Data Before the Page Renders


A loader runs before your component mounts (great for SSR — no loading spinners on first
paint).

// src/routes/posts/$[Link]
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({


loader: async ({ params }) => {
const res = await fetch(`[Link]
return [Link]()
},
component: PostPage,
})

function PostPage() {
const post = [Link]()
return <h1>{[Link]}</h1>
}

6. Server Functions — Your “Backend” Without a Separate


Server
This is TanStack Start’s headline feature: write a function that only ever runs on the server,
and call it directly from your React component like a normal function.

// src/routes/[Link]
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'

const getServerTime = createServerFn({ method: 'GET' }).handler(async () => {


return new Date().toISOString() // this code NEVER reaches the browser bundle
})

export const Route = createFileRoute('/')({


loader: () => getServerTime(),
component: HomePage,
})

function HomePage() {
const time = [Link]()
return <p>Server time: {time}</p>
}

Compare to Node/Express thinking: this replaces writing a separate /api/time route +


fetch() call — it’s like an RPC call baked into the framework.

Server Function with Input (e.g. form submission)

import { createServerFn } from '@tanstack/react-start'

const createPost = createServerFn({ method: 'POST' })


.validator((data: { title: string }) => data)
.handler(async ({ data }) => {
// save to database here
return { id: [Link](), title: [Link] }
})

7. Server Routes — True API Endpoints (for external clients,


webhooks, etc.)
If you need a classic REST-style API endpoint (not just called from your own React app), add
a server property to a route:

// src/routes/api/[Link]
import { createFileRoute } from '@tanstack/react-router'
import { json } from '@tanstack/react-start'

export const Route = createFileRoute('/api/hello')({


server: {
handlers: {
GET: () => json({ message: 'Hello from API route!' }),
POST: async ({ request }) => {
const body = await [Link]()
return json({ received: body })
},
},
},
})

This behaves exactly like an Express route — visiting /api/hello in the browser or via
fetch /Postman hits this handler directly.

8. Full CRUD Example (server functions + a page)

// src/routes/[Link]
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { useState } from 'react'

// --- fake in-memory "database" ---


let todos: { id: number; text: string }[] = []

// CREATE
const addTodo = createServerFn({ method: 'POST' })
.validator((text: string) => text)
.handler(async ({ data }) => {
const todo = { id: [Link](), text: data }
[Link](todo)
return todo
})

// READ
const getTodos = createServerFn({ method: 'GET' }).handler(async () => todos)

// UPDATE
const updateTodo = createServerFn({ method: 'POST' })
.validator((data: { id: number; text: string }) => data)
.handler(async ({ data }) => {
todos = [Link]((t) => ([Link] === [Link] ? { ...t, text: [Link] } : t))
return todos
})

// DELETE
const deleteTodo = createServerFn({ method: 'POST' })
.validator((id: number) => id)
.handler(async ({ data }) => {
todos = [Link]((t) => [Link] !== data)
return todos
})

export const Route = createFileRoute('/todos')({


loader: () => getTodos(),
component: TodosPage,
})

function TodosPage() {
const initialTodos = [Link]()
const [todos, setTodos] = useState(initialTodos)
const [text, setText] = useState('')

return (
<div>
<input value={text} onChange={(e) => setText([Link])} />
<button
onClick={async () => {
const newTodo = await addTodo({ data: text })
setTodos([...todos, newTodo])
setText('')
}}
>
Add
</button>
<ul>
{[Link]((t) => (
<li key={[Link]}>
{[Link]}
<button onClick={async () => setTodos(await deleteTodo({ data: [Link] }))}>
Delete
</button>
</li>
))}
</ul>
</div>
)
}
9. Quick Reference — Routing Cheat Sheet

File URL Matched

routes/[Link] /

routes/[Link] /about

routes/posts/[Link] /posts

routes/posts/$[Link] /posts/:postId (dynamic)

routes/[Link] + posts/ folder Layout wrapping all /posts/*

routes/_layout.tsx Pathless layout (no URL segment)

routes/files/$.tsx Splat/catch-all route

routes/api/[Link] (with server ) Real API endpoint /api/hello

Concept Node/Express-style thinking TanStack Start equivalent

[Link]('/posts/:id',
Routing posts/$[Link] file
...)

API endpoint Express route handler [Link] in a route file

Fetching before
Manual useEffect + fetch loader
render

createServerFn() called
Calling backend logic fetch('/api/...")
directly

10. Run, Build & Deploy

npm run dev # local dev server


npm run build # production build
npm run start # run the production build
Start can deploy to Netlify, Vercel, Node servers, Cloudflare Workers, and more — each with
a small adapter/plugin (e.g. @netlify/vite-plugin-tanstack-start ).

That’s the full loop: install → file-based routing → dynamic params/search params → loaders
→ server functions → API routes → CRUD → deploy. Want me to extend this with TanStack
Query integration for caching/mutations, or authentication next?

You might also like