Skip to main content

Quickstart

This guide will walk you through building a simple counter app with TanStack Start. You’ll learn how to create routes, use server functions, and build a full-stack application with type-safe client-server communication.

Prerequisites

Make sure you’ve completed the Installation guide and have a TanStack Start project set up.

Creating Your First Route

Let’s start by creating the root route and a home page.

1. Create the Root Route

The root route (__root.tsx) is the layout for your entire application. It defines the HTML structure and includes essential components like HeadContent and Scripts. Create src/routes/__root.tsx:
import * as React from 'react'
import {
  HeadContent,
  Outlet,
  Scripts,
  createRootRoute,
} from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'

export const Route = createRootRoute({
  head: () => ({
    meta: [
      {
        charSet: 'utf-8',
      },
      {
        name: 'viewport',
        content: 'width=device-width, initial-scale=1',
      },
      {
        title: 'TanStack Start App',
      },
    ],
  }),
  component: RootComponent,
})

function RootComponent() {
  return (
    <RootDocument>
      <Outlet />
      <TanStackRouterDevtools />
    </RootDocument>
  )
}

function RootDocument({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  )
}
Key components:
  • HeadContent - Renders the <head> content defined in the head() function
  • Outlet - Renders child routes
  • Scripts - Includes the necessary client-side JavaScript
  • TanStackRouterDevtools - Development tools for debugging (only in development)

2. Create the Router Configuration

Create src/router.tsx to configure your router:
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function getRouter() {
  const router = createRouter({
    routeTree,
    scrollRestoration: true,
  })

  return router
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof getRouter>
  }
}
The Register interface declaration enables full TypeScript support for your routes throughout your application.

Building a Counter with Server Functions

Now let’s build a counter that persists its value on the server. This demonstrates how to use server functions for type-safe client-server communication.

3. Create the Home Page

Create src/routes/index.tsx:
import * as fs from 'node:fs'
import { useRouter, createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'

const filePath = 'count.txt'

async function readCount() {
  return parseInt(
    await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
  )
}

const getCount = createServerFn({ method: 'GET' }).handler(() => {
  return readCount()
})

const updateCount = createServerFn({ method: 'POST' })
  .inputValidator((addBy: number) => addBy)
  .handler(async ({ data }) => {
    const count = await readCount()
    await fs.promises.writeFile(filePath, `${count + data}`)
  })

export const Route = createFileRoute('/')({ 
  component: Home,
  loader: async () => await getCount(),
})

function Home() {
  const router = useRouter()
  const state = Route.useLoaderData()

  return (
    <div style={{ padding: '20px' }}>
      <h1>Counter: {state}</h1>
      <button
        onClick={() => {
          updateCount({ data: 1 }).then(() => {
            router.invalidate()
          })
        }}
      >
        Add 1
      </button>
    </div>
  )
}
What’s happening here:
  1. Server Functions: getCount and updateCount are server functions that run exclusively on the server
  2. File System Access: We’re using Node.js fs module to read/write a file—this code never runs on the client
  3. Input Validation: inputValidator ensures type-safety for the data sent to the server
  4. Loader: The loader runs on the server during SSR and fetches the initial count
  5. Client Interaction: Clicking the button calls the server function and invalidates the router to refetch data

4. Start the Development Server

npm run dev
Visit http://localhost:3000 and you should see your counter app. Click the button and watch the count increase—the value persists even when you refresh the page because it’s stored on the server.

Adding Navigation and Multiple Pages

Let’s add another page to demonstrate routing.

5. Create an About Page

Create src/routes/about.tsx:
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/about')({
  component: About,
})

function About() {
  return (
    <div style={{ padding: '20px' }}>
      <h1>About</h1>
      <p>This is a TanStack Start application.</p>
    </div>
  )
}

6. Add Navigation to Root Route

Update src/routes/__root.tsx to add a navigation menu:
import { Link } from '@tanstack/react-router'

function RootComponent() {
  return (
    <RootDocument>
      <nav style={{ padding: '10px', borderBottom: '1px solid #ccc' }}>
        <Link to="/" style={{ marginRight: '10px' }}>
          Home
        </Link>
        <Link to="/about">
          About
        </Link>
      </nav>
      <Outlet />
      <TanStackRouterDevtools />
    </RootDocument>
  )
}
The Link component provides type-safe navigation—TypeScript will error if you try to link to a route that doesn’t exist.

Creating API Routes

API routes let you create REST endpoints that can be called from anywhere, not just your React components.

7. Create an API Route

Create src/routes/api/hello.ts:
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/hello')({
  server: {
    handlers: {
      GET: async ({ request }) => {
        return Response.json({
          message: 'Hello from TanStack Start!',
          timestamp: new Date().toISOString(),
        })
      },
    },
  },
})
You can now access this API at http://localhost:3000/api/hello.

8. Call the API from Your Component

Update src/routes/about.tsx to fetch from the API:
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/about')({
  component: About,
  loader: async () => {
    const response = await fetch('/api/hello')
    return await response.json()
  },
})

function About() {
  const data = Route.useLoaderData()

  return (
    <div style={{ padding: '20px' }}>
      <h1>About</h1>
      <p>This is a TanStack Start application.</p>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

Working with Dynamic Routes

Dynamic routes let you create pages with variable URL segments.

9. Create a Dynamic Route

Create src/routes/posts/$postId.tsx:
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'

const fetchPost = createServerFn({ method: 'GET' })
  .inputValidator((postId: string) => postId)
  .handler(async ({ data: postId }) => {
    // Simulate fetching a post
    return {
      id: postId,
      title: `Post ${postId}`,
      content: `This is the content for post ${postId}`,
    }
  })

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => await fetchPost({ data: params.postId }),
  component: Post,
})

function Post() {
  const post = Route.useLoaderData()

  return (
    <div style={{ padding: '20px' }}>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  )
}
Create src/routes/posts/index.tsx for a list of posts:
import { Link, createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/')({
  component: Posts,
})

function Posts() {
  const posts = [1, 2, 3, 4, 5]

  return (
    <div style={{ padding: '20px' }}>
      <h1>Posts</h1>
      <ul>
        {posts.map((id) => (
          <li key={id}>
            <Link to="/posts/$postId" params={{ postId: String(id) }}>
              Post {id}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  )
}
Update the root navigation to include Posts:
<Link to="/posts" style={{ marginRight: '10px' }}>
  Posts
</Link>

Building for Production

When you’re ready to deploy:

10. Build Your App

npm run build
This creates optimized bundles in the .output directory:
  • .output/client - Client-side assets (HTML, CSS, JS)
  • .output/server - Server-side code

11. Run the Production Server

npm run start
Your app is now running in production mode.

Next Steps

You’ve built a full-stack application with TanStack Start. Here’s what you’ve learned:
  • Creating routes with file-based routing
  • Using server functions for type-safe client-server communication
  • Building API routes for REST endpoints
  • Working with dynamic routes and navigation
  • Loading data with loaders and SSR

Learn More

Explore these topics to build more advanced applications:
  • Data Loading - Learn about loaders, prefetching, and caching
  • Authentication - Implement user authentication and protected routes
  • Middleware - Add logging, authentication, and custom logic to routes
  • Streaming - Stream data to improve perceived performance
  • Deployment - Deploy to Vercel, Netlify, Cloudflare, and more

Example Projects

Check out the examples directory in the TanStack Router repository for more complete examples:
  • start-basic - A basic Start application
  • start-counter - The counter example from this guide
  • start-basic-auth - Authentication with sessions
  • start-basic-react-query - Integration with TanStack Query
  • start-clerk-basic - Authentication with Clerk
  • start-supabase-basic - Full-stack app with Supabase
Happy building with TanStack Start!

Build docs developers (and LLMs) love