Skip to main content

How to Set Up a Modern React Project in 2026

·PkgPulse Team
0

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)

Why This Stack

Create React App served its purpose but is now officially deprecated — the React team no longer recommends it. The JavaScript ecosystem moved on: Vite is 10-100x faster for development, ESM is now the standard, and TypeScript is the default rather than an optional add-on.

The modern stack reflects what experienced React developers actually use in 2026:

Vite replaced webpack because it uses native ESM in development — no bundling, just instant module serving. Cold starts in ~200ms, HMR in ~50ms. Webpack hot reloads can take 2-10 seconds in large projects.

Biome replaces ESLint + Prettier because it's a single Rust-based tool that handles both, runs 30x faster, and eliminates the configuration headache of making ESLint and Prettier not conflict with each other.

TanStack Query replaces Redux for server state because 80% of what people stored in Redux was actually remote data. TanStack Query handles fetching, caching, background refetching, and optimistic updates — better than any Redux pattern.

Zustand replaces Redux for client state because it's simpler (a store is just a function), smaller (~1KB vs Redux's ~8KB + react-redux), and requires no boilerplate.


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

shadcn/ui components are copied into your project at src/components/ui/. You own the code — modify it freely. This is unlike traditional component libraries where you're locked into the library's API.


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;
}

Step 9: TypeScript Path Aliases

npm install -D vite-tsconfig-paths
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
// vite.config.ts
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  plugins: [react(), tsconfigPaths()],
});

Path aliases let you write import { Button } from '@/components/ui/button' instead of import { Button } from '../../../components/ui/button'. They also make refactoring easier — moving a file doesn't require updating relative import paths.


Common Mistakes to Avoid

Don't use Redux for API data. If you reach for Redux to store data you fetched from an API, use TanStack Query instead. Redux is for client state (UI state, user preferences, form state). Remote data has its own lifecycle (stale, loading, error, refetching) that TanStack Query handles.

Don't skip TypeScript path aliases. Starting a project without path aliases means every moved file requires hunting down relative imports. Set them up on day one.

Don't forget suppressHydrationWarning if using SSR. If you later add Next.js or any SSR layer, your components that read localStorage or window need to handle SSR carefully to avoid hydration mismatches.

Don't co-locate all state in URL. URL state is great for shareable state (filters, current page, search query). But not everything belongs in the URL — transient UI state like modal open/close belongs in Zustand.


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. Also see our Vitest vs Jest guide for testing setup details and best form libraries for React for form patterns.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.