tanstack-start-project-setup

Installation
SKILL.md

TanStack Start Project Setup

TanStack Start is a full-stack React framework built on TanStack Router, Vinxi, and Vite.

When to Use

  • Creating a new TanStack Start project
  • Configuring router settings
  • Setting up TanStack Query
  • Adding TypeScript configuration
  • Configuring for Netlify deployment

Quick Start

# Create new project
npx create-tanstack-start@latest my-app

# Or with specific template
npx create-tanstack-start@latest my-app --template basic

Project Structure

my-app/
├── src/
│   ├── routes/
│   │   ├── __root.tsx        # Root layout
│   │   ├── index.tsx         # Home page (/)
│   │   └── ...
│   ├── server/
│   │   └── *.functions.ts    # Server functions
│   ├── components/
│   ├── lib/
│   ├── router.tsx            # Router configuration
│   └── routeTree.gen.ts      # Auto-generated
├── public/
├── app.config.ts             # TanStack Start config
├── package.json
├── tsconfig.json
└── netlify.toml              # Netlify deployment

Essential Files

app.config.ts

// app.config.ts
import { defineConfig } from '@tanstack/react-start/config';

export default defineConfig({
  // Vite configuration
  vite: {
    // Add Vite plugins here
    plugins: [],
  },
  
  // Server configuration
  server: {
    // Server preset (netlify, vercel, node, etc.)
    preset: 'netlify',
  },
  
  // Router configuration
  tsr: {
    // Route file location
    routesDirectory: './src/routes',
    
    // Generated route tree location
    generatedRouteTree: './src/routeTree.gen.ts',
  },
});

router.tsx

// src/router.tsx
import { createRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';

export function getRouter() {
  const router = createRouter({
    routeTree,
    
    // Enable scroll restoration
    scrollRestoration: true,
    
    // Preload on hover
    defaultPreload: 'intent',
    
    // Preload stale time
    defaultPreloadStaleTime: 0,
    
    // Default error component
    defaultErrorComponent: ({ error }) => (
      <div>Error: {error.message}</div>
    ),
    
    // Default pending component
    defaultPendingComponent: () => <div>Loading...</div>,
    
    // Default not found component
    defaultNotFoundComponent: () => <div>Not Found</div>,
  });
  
  return router;
}

// Type registration for full type safety
declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof getRouter>;
  }
}

Root Route (__root.tsx)

// src/routes/__root.tsx
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/react-router';
import type { ReactNode } from 'react';

export const Route = createRootRoute({
  head: () => ({
    meta: [
      { charSet: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { title: 'My TanStack Start App' },
      { name: 'description', content: 'Built with TanStack Start' },
    ],
    links: [
      { rel: 'stylesheet', href: '/styles.css' },
      { rel: 'icon', href: '/favicon.ico' },
    ],
  }),
  component: RootComponent,
});

function RootComponent() {
  return (
    <html lang="en">
      <head>
        <HeadContent />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  );
}

TanStack Query Integration

Installation

npm install @tanstack/react-query

Setup

// src/router.tsx
import { createRouter } from '@tanstack/react-router';
import { QueryClient } from '@tanstack/react-query';
import { routeTree } from './routeTree.gen';

export function getRouter() {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 1000 * 60, // 1 minute
        gcTime: 1000 * 60 * 5, // 5 minutes
      },
    },
  });
  
  const router = createRouter({
    routeTree,
    context: {
      queryClient,
    },
  });
  
  return router;
}

declare module '@tanstack/react-router' {
  interface Register {
    router: ReturnType<typeof getRouter>;
  }
}

Root with Query Provider

// src/routes/__root.tsx
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

export const Route = createRootRouteWithContext<{
  queryClient: QueryClient;
}>()({
  component: RootComponent,
});

function RootComponent() {
  const { queryClient } = Route.useRouteContext();
  
  return (
    <QueryClientProvider client={queryClient}>
      <html lang="en">
        <head>
          <HeadContent />
        </head>
        <body>
          <Outlet />
          <Scripts />
          <ReactQueryDevtools />
        </body>
      </html>
    </QueryClientProvider>
  );
}

TypeScript Configuration

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "noEmit": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "paths": {
      "~/*": ["./src/*"]
    }
  },
  "include": ["src/**/*", "app.config.ts"],
  "exclude": ["node_modules"]
}

Netlify Deployment

netlify.toml

[build]
  command = "npm run build"
  publish = ".output/public"

[build.environment]
  NODE_VERSION = "20"

# Functions directory (auto-configured by TanStack Start)
[functions]
  directory = ".output/server"

app.config.ts for Netlify

// app.config.ts
import { defineConfig } from '@tanstack/react-start/config';

export default defineConfig({
  server: {
    preset: 'netlify',
  },
});

Environment Variables

.env Files

# .env (local development)
DATABASE_URL=postgres://localhost:5432/mydb
VITE_APP_NAME=My App

# .env.production (production)
DATABASE_URL=postgres://prod-server:5432/mydb
VITE_APP_NAME=My App

Accessing Variables

// Server-side (server functions, loaders on server)
const dbUrl = process.env.DATABASE_URL;

// Client-side (must be prefixed with VITE_)
const appName = import.meta.env.VITE_APP_NAME;

CSS/Styling Setup

Tailwind CSS

npm install tailwindcss @tailwindcss/vite
// app.config.ts
import { defineConfig } from '@tanstack/react-start/config';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  vite: {
    plugins: [tailwindcss()],
  },
});
/* src/styles.css */
@import 'tailwindcss';

CSS Modules

// Component.tsx
import styles from './Component.module.css';

export function Component() {
  return <div className={styles.container}>Hello</div>;
}

Development Scripts

// package.json
{
  "scripts": {
    "dev": "vinxi dev",
    "build": "vinxi build",
    "start": "vinxi start",
    "lint": "eslint src/",
    "typecheck": "tsc --noEmit"
  }
}

Recommended Package.json

{
  "name": "my-tanstack-app",
  "type": "module",
  "scripts": {
    "dev": "vinxi dev",
    "build": "vinxi build",
    "start": "vinxi start"
  },
  "dependencies": {
    "@tanstack/react-query": "^5.0.0",
    "@tanstack/react-router": "^1.0.0",
    "@tanstack/react-start": "^1.0.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "vinxi": "^0.5.0",
    "zod": "^3.23.0"
  },
  "devDependencies": {
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "typescript": "^5.6.0"
  }
}

Database Setup (Drizzle)

npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});
// src/db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });

Common Configurations

Custom 404 Page

// src/routes/__root.tsx
export const Route = createRootRoute({
  notFoundComponent: () => (
    <div className="not-found">
      <h1>404 - Page Not Found</h1>
      <Link to="/">Go Home</Link>
    </div>
  ),
});

Global Error Boundary

// src/routes/__root.tsx
export const Route = createRootRoute({
  errorComponent: ({ error, reset }) => (
    <div className="error-page">
      <h1>Something went wrong</h1>
      <p>{error.message}</p>
      <button onClick={reset}>Try Again</button>
    </div>
  ),
});

Loading States

// src/routes/__root.tsx
export const Route = createRootRoute({
  pendingComponent: () => (
    <div className="loading">
      <span className="spinner" />
      Loading...
    </div>
  ),
});

Checklist for New Projects

  • Create project with create-tanstack-start
  • Configure app.config.ts with Netlify preset
  • Set up TypeScript paths (~/)
  • Configure TanStack Query if needed
  • Set up CSS solution (Tailwind recommended)
  • Create .env for local development
  • Configure netlify.toml for deployment
  • Set up database if needed (Drizzle + Neon)
  • Add ESLint configuration
  • Set environment variables in Netlify dashboard
Related skills
Installs
1
GitHub Stars
2
First Seen
5 days ago