Scaffold an opinionated React 19 + Vite app — TypeScript strict, TanStack Router, TanStack Query, Zustand, Tailwind v4, Shadcn/ui, Vitest, Husky
Scaffold a production-grade React 19 application. Follow every instruction below exactly. Do not skip steps. Do not ask before proceeding — just build it.
Project name: $ARGUMENTS
@/ → src/)npm create vite@latest {name} -- --template react-ts
cd {name}
npm install
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noImplicitOverride": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"useUnknownInCatchVariables": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@features/*": ["src/features/*"],
"@hooks/*": ["src/hooks/*"],
"@lib/*": ["src/lib/*"],
"@stores/*": ["src/stores/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
# Routing
npm install @tanstack/react-router
# Server state
npm install @tanstack/react-query @tanstack/react-query-devtools
# Client state
npm install zustand immer
# UI
npm install tailwindcss @tailwindcss/vite
npm install class-variance-authority clsx tailwind-merge lucide-react
# Forms & validation
npm install react-hook-form zod @hookform/resolvers
# Dev dependencies
npm install -D @types/react @types/react-dom
npm install -D vitest @vitest/coverage-v8 jsdom
npm install -D @testing-library/react @testing-library/user-event @testing-library/jest-dom
npm install -D @tanstack/router-devtools
npm install -D eslint @eslint/js typescript-eslint eslint-plugin-react eslint-plugin-react-hooks
npm install -D prettier prettier-plugin-tailwindcss
npm install -D husky lint-staged
npm install -D @vitejs/plugin-react
vite.config.ts:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import { resolve } from 'path';
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
export default defineConfig({
plugins: [
TanStackRouterVite(),
react(),
tailwindcss(),
],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, 'src/components'),
'@features': resolve(__dirname, 'src/features'),
'@hooks': resolve(__dirname, 'src/hooks'),
'@lib': resolve(__dirname, 'src/lib'),
'@stores': resolve(__dirname, 'src/stores'),
},
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['src/test-setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
thresholds: { lines: 80, functions: 80, branches: 80, statements: 80 },
},
},
});
eslint.config.js:
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import globals from 'globals';
export default tseslint.config(
{ ignores: ['dist', 'node_modules', '.tanstack'] },
{
extends: [js.configs.recommended, ...tseslint.configs.strictTypeChecked],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2022,
globals: globals.browser,
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
plugins: {
react: reactPlugin,
'react-hooks': reactHooks,
},
rules: {
...reactPlugin.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': ['error', { allowExpressions: true }],
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
'no-console': 'warn',
},
settings: { react: { version: 'detect' } },
},
);
.prettierrc:
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"plugins": ["prettier-plugin-tailwindcss"]
}
npx husky init
.husky/pre-commit:
npx lint-staged
package.json (add):
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,json,md}": ["prettier --write"]
}
}
src/index.css:
@import "tailwindcss";
src/
├── components/
│ └── ui/ ← shadcn components go here
│ ├── button.tsx
│ └── input.tsx
├── features/
│ └── home/
│ ├── index.ts
│ ├── HomeView.tsx
│ └── HomeView.test.tsx
├── hooks/
│ └── useDebounce.ts
├── lib/
│ ├── api.ts ← axios/fetch client
│ ├── utils.ts ← cn() helper
│ └── validations/
├── routes/
│ ├── __root.tsx ← TanStack Router root
│ └── index.tsx
├── stores/
│ └── useAppStore.ts
├── types/
│ └── index.ts
├── App.tsx
├── main.tsx
└── test-setup.ts
src/lib/utils.ts:
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
src/routes/__root.tsx:
import { createRootRoute, Outlet } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
export const Route = createRootRoute({
component: () => (
<>
<Outlet />
<TanStackRouterDevtools />
<ReactQueryDevtools />
</>
),
});
src/routes/index.tsx:
import { createFileRoute } from '@tanstack/react-router';
import { HomeView } from '@features/home';
export const Route = createFileRoute('/')({
component: HomeView,
});
src/main.tsx:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider, createRouter } from '@tanstack/react-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { routeTree } from './routeTree.gen';
import './index.css';
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 60_000, retry: 2 },
},
});
const router = createRouter({ routeTree, context: { queryClient } });
declare module '@tanstack/react-router' {
interface Register { router: typeof router; }
}
const root = document.getElementById('root');
if (!root) throw new Error('Root element not found');
createRoot(root).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</StrictMode>,
);
src/test-setup.ts:
import '@testing-library/jest-dom';
npx shadcn@latest init
# Choose: Default style, Zinc base color, CSS variables
Add first components:
npx shadcn@latest add button input label card badge
package.json:
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,css,json}\"",
"typecheck": "tsc --noEmit"
}
}
npm run build succeeds with zero errorsnpm test passesnpm run lint returns zero errorsnpm run typecheck returns zero errors@/, @features/, etc.)any types in source files