React + TypeScript Production Setup: What Your Boilerplate Must Include in 2026
Every React TypeScript production boilerplate needs: Vite or Next.js, ESLint, Vitest, React Query, Zod, Tailwind. Full setup guide with folder structure and CI/CD.
React + TypeScript Production Setup: What Your Boilerplate Must Include in 2026
Starting a React TypeScript project with create-react-app is no longer viable — it was officially deprecated in 2023. The current ecosystem offers faster, more capable alternatives, but also more choices. This guide covers every dependency, configuration, and structural decision a production-grade React TypeScript project needs in 2026 — with rationale for each choice.
Why Your Boilerplate Choices Matter
A poor initial setup costs days of refactoring when your team scales or your app grows. A solid boilerplate:
- Enforces consistent code style (reducing review friction)
- Catches bugs before they reach production (types + tests)
- Keeps build times under 30 seconds locally
- Deploys reliably from day one
The decisions you make in the first hour determine maintainability for the next two years.
Vite vs Next.js: The First Decision
| Feature | Vite 6 | Next.js 15 | |---------|--------|-----------| | Rendering | Client-side only (SPA) | SSR, SSG, ISR, RSC | | API routes | No (use a separate backend) | Yes (Route Handlers) | | Build speed (dev) | Instant HMR (under 50ms) | Fast, but slightly slower | | Build speed (prod) | Rollup-based, very fast | Webpack/Turbopack | | SEO | Requires prerendering setup | Built-in | | Deployment | Any static host | Vercel-optimized, but portable | | Complexity | Low | Medium | | Bundle size baseline | ~40 KB | ~90 KB |
Use Vite when: SPA, dashboard, internal tool, Electron app, Chrome extension.
Use Next.js when: Marketing site + app in one, blog, e-commerce, anything requiring server-side rendering for SEO.
Initialize a Vite Project
pnpm create vite my-app --template react-ts
cd my-app
pnpm install
Initialize a Next.js Project
pnpm create next-app my-app --typescript --tailwind --eslint --app --src-dir
TypeScript Configuration
Your tsconfig.json must enable strict mode from day one:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
noUncheckedIndexedAccess adds undefined to array index access types, preventing the most common runtime crashes in TypeScript apps. Enable it from the start.
Essential Dependencies
Core Dependencies Table
| Package | Version (2025) | Purpose | Required | |---------|---------------|---------|----------| | react | 19.x | UI library | Yes | | react-dom | 19.x | DOM renderer | Yes | | @tanstack/react-query | 5.x | Server state management | Yes | | zod | 3.x | Runtime schema validation | Yes | | react-hook-form | 7.x | Form state management | For forms | | zustand | 5.x | Client state management | If needed | | axios | 1.x | HTTP client | Optional (fetch works) |
Dev Dependencies Table
| Package | Purpose | Required | |---------|---------|----------| | typescript | TypeScript compiler | Yes | | vite / next | Build tool | Yes | | vitest | Unit/integration tests | Yes | | @testing-library/react | Component testing | Yes | | @testing-library/user-event | User interaction simulation | Yes | | eslint | Linter | Yes | | @typescript-eslint/eslint-plugin | TypeScript ESLint rules | Yes | | eslint-plugin-react-hooks | Hooks rules enforcement | Yes | | prettier | Code formatter | Yes | | tailwindcss | Utility-first CSS | Recommended | | husky | Git hooks | Recommended | | lint-staged | Run linters on staged files | Recommended |
Install everything at once:
pnpm add @tanstack/react-query zod react-hook-form zustand
pnpm add -D vitest @testing-library/react @testing-library/user-event @testing-library/jest-dom
pnpm add -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
pnpm add -D eslint-plugin-react-hooks eslint-plugin-jsx-a11y
pnpm add -D prettier tailwindcss postcss autoprefixer
pnpm add -D husky lint-staged
Folder Structure
A feature-based folder structure scales better than a type-based one. Avoid the common mistake of organizing by components/, hooks/, utils/ at the top level — it breaks down at 20+ features.
src/
├── app/ # Next.js App Router pages or Vite app entry
├── features/ # Feature modules (colocated)
│ ├── auth/
│ │ ├── components/ # Auth-specific components
│ │ ├── hooks/ # useAuth, useSession
│ │ ├── api/ # Auth API calls
│ │ ├── schemas/ # Zod schemas for auth data
│ │ └── index.ts # Public API of the feature
│ ├── dashboard/
│ └── settings/
├── shared/ # Truly shared across features
│ ├── components/ # Button, Input, Modal, etc.
│ ├── hooks/ # useDebounce, useLocalStorage
│ ├── lib/ # queryClient, axios instance, etc.
│ └── types/ # Global TypeScript types
├── styles/ # Global CSS / Tailwind base
└── main.tsx # Entry point
Rule: A file in features/auth/ can import from shared/. A file in shared/ must never import from features/. Features can only import other features through their index.ts public API.
Linting and Formatting
ESLint Configuration (flat config, ESLint 9)
// eslint.config.js
import tseslint from '@typescript-eslint/eslint-plugin'
import reactHooks from 'eslint-plugin-react-hooks'
import jsxA11y from 'eslint-plugin-jsx-a11y'
export default [
{
plugins: { '@typescript-eslint': tseslint, 'react-hooks': reactHooks, 'jsx-a11y': jsxA11y },
rules: {
...tseslint.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'no-console': ['warn', { allow: ['warn', 'error'] }],
}
}
]
Prettier Configuration
{
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2
}
Set up Husky + lint-staged to run ESLint and Prettier on every commit:
pnpm dlx husky init
echo "pnpm lint-staged" > .husky/pre-commit
// package.json
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,css}": ["prettier --write"]
}
Testing Strategy: Vitest vs Jest
| Feature | Vitest 2.x | Jest 29.x | |---------|-----------|----------| | Speed | 2–10x faster | Slower (CommonJS transform) | | ES modules | Native | Requires config | | Config file | Unified with vite.config.ts | Separate jest.config.ts | | TypeScript | Out of the box | Requires ts-jest or babel | | Coverage | Built-in (v8 or istanbul) | Requires c8 or istanbul | | Watch mode | Instant | Slower | | Next.js support | Community | Official | | Ecosystem | Growing fast | Mature |
Use Vitest for Vite projects. Use Jest for Next.js if you need maximum ecosystem compatibility, otherwise Vitest works there too.
Vitest Setup
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
thresholds: { lines: 70, functions: 70, branches: 70 },
},
},
})
// src/test/setup.ts
import '@testing-library/jest-dom'
Testing Guidelines
- Unit tests: Pure functions, hooks, Zod schemas
- Component tests: With Testing Library — test behavior, not implementation
- Integration tests: API + component together using MSW (Mock Service Worker)
- E2E tests: Playwright for critical user flows (login, checkout)
Target: 70% line coverage on features/ and shared/. Do not test implementation details.
Data Fetching with React Query
React Query v5 (TanStack Query) is the standard for server state in React applications. It replaces useEffect + useState for any data that comes from an API.
Setup
// src/shared/lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60, // 1 minute
retry: 1,
},
},
})
// src/main.tsx
import { QueryClientProvider } from '@tanstack/react-query'
import { queryClient } from '@/shared/lib/queryClient'
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
)
Query Pattern with Zod Validation
// src/features/users/api/getUser.ts
import { z } from 'zod'
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string(),
createdAt: z.string().datetime(),
})
export type User = z.infer<typeof UserSchema>
export async function getUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error('Failed to fetch user')
return UserSchema.parse(await res.json())
}
CI/CD Pipeline
GitHub Actions (Minimal)
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with: { node-version: '22', cache: 'pnpm' }
- run: pnpm install --frozen-lockfile
- run: pnpm tsc --noEmit
- run: pnpm lint
- run: pnpm test --run --coverage
- run: pnpm build
This pipeline runs in under 3 minutes on a standard GitHub Actions runner for a medium-sized project.
Deployment Targets
| Platform | Vite SPA | Next.js | Cost | |----------|----------|---------|------| | Vercel | Yes | Yes (native) | Free–$20/month | | Netlify | Yes | Yes | Free–$19/month | | Cloudflare Pages | Yes | Yes (adapter) | Free–$20/month | | Fly.io | Via Docker | Via Docker | $0–$variable | | AWS S3 + CloudFront | Yes | No | $1–$10/month |
Environment Variables and Type Safety
Never use process.env.VITE_API_URL directly. Validate env vars at startup with Zod:
// src/shared/lib/env.ts
import { z } from 'zod'
const EnvSchema = z.object({
VITE_API_URL: z.string().url(),
VITE_APP_ENV: z.enum(['development', 'staging', 'production']),
})
export const env = EnvSchema.parse(import.meta.env)
If a required env var is missing or malformed, the app crashes immediately with a clear error instead of silently failing at runtime.
Get a Production-Ready Boilerplate
Setting up all of this correctly takes 4–8 hours the first time. The MAG Editions React TypeScript Starter Kit ships with every configuration above pre-wired: Vite 6 + React 19, strict TypeScript, ESLint 9 flat config, Vitest + Testing Library, React Query v5, Zod, Tailwind v4, Husky, GitHub Actions, and a feature-based folder structure. Clone it and write your first feature in under 10 minutes.
Summary Checklist
- [ ] Vite 6 or Next.js 15 (not CRA)
- [ ] TypeScript strict mode enabled from day one
- [ ] Feature-based folder structure (not type-based)
- [ ] ESLint + Prettier + Husky pre-commit hook
- [ ] Vitest for unit/component tests
- [ ] React Query v5 for all server state
- [ ] Zod for runtime validation of API responses and env vars
- [ ] Tailwind CSS for styling
- [ ] GitHub Actions CI running typecheck, lint, test, build
- [ ] Coverage thresholds enforced (70% minimum)
Go further
React + TypeScript Production Starter Kit — 5 Templates Ready to Ship
Five production-ready React + TypeScript templates covering SaaS, landing pages, dashboards, and more.
View product