Skip to main content

How to Set Up a Modern React Project in 2026

·PkgPulse Team

TL;DR

The 2026 React stack: Vite + TypeScript + Biome + Vitest + TanStack Query + Zustand + shadcn/ui. Create React App is deprecated. This guide sets up a production-ready project from scratch — typed, linted, tested, and styled — using the tools developers actually choose in 2026.

Key Takeaways

  • Vite: dev server + build (not CRA, not webpack)
  • Biome: linting + formatting (not ESLint + Prettier)
  • Vitest: unit testing (not Jest)
  • TanStack Query: server state (not Redux for API data)
  • Zustand: client state (not Redux)
  • shadcn/ui: component library (copy-paste, not npm package)

Step 1: Scaffold with Vite

# Official Vite React TypeScript template
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install

# Start dev server
npm run dev
# → http://localhost:5173 in ~200ms

Step 2: Add Core Dependencies

# Routing
npm install react-router-dom

# Data fetching + server state
npm install @tanstack/react-query @tanstack/react-query-devtools

# Client state
npm install zustand

# HTTP client
npm install ky

# Form handling + validation
npm install react-hook-form @hookform/resolvers zod

# Date utilities
npm install date-fns

# Class name utilities
npm install clsx tailwind-merge

Step 3: Tailwind CSS + shadcn/ui

# Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

# Initialize shadcn/ui
npx shadcn@latest init
# Prompts: style (Default/New York), base color, CSS variables

# Add components as needed
npx shadcn@latest add button
npx shadcn@latest add input
npx shadcn@latest add form
npx shadcn@latest add dialog

Step 4: Biome (Linting + Formatting)

npm install -D --save-exact @biomejs/biome
npx @biomejs/biome init
// biome.json
{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "organizeImports": { "enabled": true },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "correctness": {
        "noUnusedVariables": "error",
        "useExhaustiveDependencies": "warn"
      },
      "suspicious": { "noConsoleLog": "warn" }
    }
  },
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "single",
      "trailingCommas": "es5"
    }
  },
  "files": { "ignore": ["dist/**", "node_modules/**"] }
}
// package.json scripts
{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "check": "biome check --apply .",
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage"
  }
}

Step 5: Vitest + Testing Library

npm install -D vitest @vitest/ui jsdom
npm install -D @testing-library/react @testing-library/user-event @testing-library/jest-dom
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  plugins: [react(), tsconfigPaths()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./src/test/setup.ts'],
  },
});
// src/test/setup.ts
import '@testing-library/jest-dom';

Step 6: TanStack Query Setup

// src/main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000,    // 1 minute
      retry: 1,
    },
  },
});

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  </StrictMode>
);

Step 7: Project Structure

src/
├── components/
│   ├── ui/           # shadcn/ui components (auto-generated)
│   └── [feature]/    # Feature-specific components
├── pages/            # Route-level components
├── hooks/            # Custom React hooks
├── lib/
│   ├── api.ts        # ky instance + API helpers
│   ├── queryClient.ts
│   └── utils.ts      # cn() and other utilities
├── stores/           # Zustand stores
├── test/
│   └── setup.ts
├── types/            # TypeScript type definitions
├── App.tsx
└── main.tsx

Step 8: Environment Variables

# .env.local
VITE_API_URL=http://localhost:3001
VITE_APP_NAME="My App"
// src/vite-env.d.ts — type your env vars
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_URL: string;
  readonly VITE_APP_NAME: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

Final Checklist

✅ Vite + React + TypeScript — scaffolded
✅ Tailwind CSS + shadcn/ui — styled
✅ Biome — linting and formatting configured
✅ Vitest + Testing Library — test infrastructure ready
✅ TanStack Query — server state management
✅ Zustand — client state management
✅ React Router — routing
✅ React Hook Form + Zod — forms and validation
✅ Environment variables typed
✅ Path aliases (tsconfigPaths)

Compare React setup tools on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.