$ npx rulesync-cli pull✓ Wrote CLAUDE.md (2 rulesets)# Coding Standards- Always use async/await- Prefer named exports
Rule Writing

CLAUDE.md for React with Vite

React + Vite is the default for new SPAs. AI mixes Next.js patterns into Vite projects. Rules for client-side routing, state management, and Vite-specific conventions.

7 min read·August 14, 2024

AI generates Next.js server patterns in client-side Vite projects

Client-side SPA rules: React Router, TanStack Query, Zustand, and Vite config

Why React + Vite Needs Its Own Rules

React with Vite is the standard setup for single-page applications (SPAs) — client-side rendered apps without a server framework. AI assistants frequently confuse this with Next.js: they generate getServerSideProps, server components, API routes, and other server-side patterns that don't exist in a Vite project. The result is code that doesn't compile.

The other common mistake is treating Vite like Create React App (CRA). AI generates CRA conventions: react-scripts, REACT_APP_ environment variables, and webpack-specific patterns. Vite uses a different dev server, different env variable prefix (VITE_), and different build pipeline (Rollup under the hood, not webpack).

These rules are for React 18/19 with Vite as the build tool — a client-side SPA without server-side rendering. If you use Vite with a server framework (like Vite + Express), add server-specific rules on top of these.

Rule 1: This Is a Client-Side SPA

The rule: 'This is a client-side React application built with Vite. There is no server-side rendering, no React Server Components, no getServerSideProps, no server actions. All code runs in the browser. Data fetching happens via HTTP requests to an external API. Never generate server-side patterns — they don't exist in this project.'

For data fetching: 'Use TanStack Query (React Query) for all API data fetching. Define query keys and fetch functions. Use useQuery for reads, useMutation for writes, useInfiniteQuery for paginated lists. Handle loading, error, and success states in components. Never fetch data in useEffect manually — TanStack Query handles caching, deduplication, and refetching.'

This declarative rule is the most important: 'There is no server.' AI assistants default to server-side patterns because Next.js dominates their training data. One sentence forces all code to be client-side.

  • No SSR, no RSC, no getServerSideProps, no server actions — this is an SPA
  • TanStack Query for all data fetching — never useEffect + fetch manually
  • All code runs in the browser — API calls to external backend
  • No API routes — the backend is a separate service
  • Environment variables use VITE_ prefix — not REACT_APP_
⚠️ There Is No Server

'This is a client-side SPA — no SSR, no RSC, no getServerSideProps.' This one sentence prevents every server-side pattern the AI tries to generate. Without it, AI defaults to Next.js patterns that don't compile in Vite.

Rule 2: Client-Side Routing with React Router

The rule: 'Use React Router v6 for all routing. Define routes in a central router configuration using createBrowserRouter and RouterProvider. Use loader functions for data fetching tied to routes. Use lazy() for code-splitting routes. Use <Link> and useNavigate for navigation — never window.location or <a href> for internal links.'

For route structure: 'Define a root route with a layout component (nav, sidebar). Nest child routes for pages. Use <Outlet /> in layout components to render child routes. Use errorElement for route-level error handling. Use index routes for default content in a layout.'

For protected routes: 'Use a layout route with an auth check for protected sections: if not authenticated, redirect to /login. Use React Router's loader functions to check auth before rendering. Never check auth inside individual page components — centralize in the route configuration.'

Rule 3: State Management

The rule: 'Use Zustand for client state management (UI state, form state, local preferences). Use TanStack Query for server state (API data, cached responses). Never store server data in Zustand — let TanStack Query manage it. For form state, use React Hook Form with Zod validation. The separation: Zustand for client state, TanStack Query for server state, React Hook Form for form state.'

For Zustand: 'Create small, focused stores: useAuthStore, useThemeStore, useUIStore. Never create one giant global store. Use selectors to prevent unnecessary re-renders: const name = useAuthStore(s => s.user.name). Use persist middleware for state that should survive page refreshes (theme preference, sidebar collapsed).'

AI assistants often use Redux (over-engineered for most SPAs), Context API for everything (causes unnecessary re-renders), or useEffect + useState for server data (loses caching and deduplication). These three tools cover every state management need cleanly.

  • Zustand: client state (UI, preferences, local data) — small focused stores
  • TanStack Query: server state (API data, cached responses) — never in Zustand
  • React Hook Form + Zod: form state + validation — controlled without performance cost
  • No Redux unless genuinely needed — no Context for frequently changing state
  • persist middleware for state that survives refresh (theme, sidebar)
💡 Three State Tools

Zustand for client state, TanStack Query for server state, React Hook Form for forms. This covers every state need without Redux's boilerplate, Context's re-renders, or manual useEffect fetching.

Rule 4: Vite Configuration and Environment

The rule: 'Vite config lives in vite.config.ts. Use TypeScript for configuration. Configure path aliases: resolve: { alias: { "@": path.resolve(__dirname, "src") } } and match in tsconfig.json paths. Use environment variables with VITE_ prefix: VITE_API_URL, VITE_APP_VERSION. Access in code with import.meta.env.VITE_API_URL — never process.env (that's Node.js).'

For plugins: 'Use @vitejs/plugin-react for React support. Use vite-plugin-svgr for SVG-as-components. Use vite-tsconfig-paths for TypeScript path resolution. Keep plugins minimal — Vite's defaults are good. Never add plugins that duplicate Vite's built-in capabilities.'

For environment files: '.env for all environments, .env.local for local overrides (in .gitignore), .env.production for production values. Vite loads these automatically based on the mode. Use import.meta.env.MODE to check the current mode. Never commit .env.local — it contains developer-specific settings.'

ℹ️ VITE_ Not REACT_APP_

Vite uses VITE_ prefix for env vars, accessed via import.meta.env — not process.env. AI generates REACT_APP_ (Create React App) or process.env (Node.js) constantly. Declare the prefix in your rules.

Rule 5: Build Optimization

The rule: 'Use lazy() and Suspense for route-level code splitting — every route is a separate chunk. Analyze bundle with rollup-plugin-visualizer. Tree-shake imports: import { format } from "date-fns" not import * as dateFns. Use CSS Modules or Tailwind — never global CSS that conflicts across components. Set build target in vite.config: build: { target: "es2022" } for modern browsers.'

For assets: 'Import images and SVGs as modules: import logo from "./logo.svg". Vite handles hashing and optimization. Use the public/ directory for assets that shouldn't be processed (favicons, robots.txt). For large images, use responsive formats (WebP, AVIF) and lazy loading.'

For production: 'Use vite build with minification (default). Enable gzip/brotli compression on your CDN/server. Set long cache headers on hashed assets (1 year). Use vite preview for local production testing before deploying.'

  • Route-level code splitting with lazy() + Suspense — every route a chunk
  • rollup-plugin-visualizer for bundle analysis — tree-shake all imports
  • CSS Modules or Tailwind — never global CSS that leaks between components
  • Import assets as modules — public/ for unprocessed assets
  • build.target: es2022 — gzip/brotli on CDN — long cache on hashed assets

Complete React + Vite Rules Template

Consolidated rules for React SPA projects using Vite.

  • Client-side SPA: no SSR, no RSC, no server patterns — all code runs in browser
  • TanStack Query for API data — Zustand for client state — React Hook Form for forms
  • React Router v6: createBrowserRouter, loaders, lazy routes, centralized auth
  • VITE_ env vars — import.meta.env access — .env.local in .gitignore
  • Path aliases in vite.config + tsconfig — @vitejs/plugin-react for JSX
  • Route-level code splitting — tree-shaking — rollup-plugin-visualizer
  • Vitest for testing — @testing-library/react — msw for API mocking
  • ESLint + Prettier — husky pre-commit — vite preview for production testing