React-проект от идеи до деплоя через Claude Code

React — главная жертва «AI-кодинга наугад». Компоненты плодятся, хуки дублируются, styled-жопа копируется 20 раз. С Claude Code это лечится, но только при правильной настройке.

React-проект от идеи до деплоя через Claude Code

Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper

Стартовый каркас

npm create vite@latest my-app -- --template react-ts cd my-app && npm install

Потом в Claude:

Добавь: - TanStack Query для серверного state - React Hook Form + Zod для форм - shadcn/ui для компонентов - React Router v7 - Vitest + React Testing Library - ESLint + Prettier + husky pre-commit - Структура: src/{components,features,hooks,lib,api,pages}/

CLAUDE.md для React

Стек - React 19, Vite, TypeScript strict - TanStack Query для API - React Hook Form + Zod для форм - shadcn/ui + Tailwind CSS ## Структура - `src/features//` — фича (hooks, компоненты, типы) — инкапсулируется - `src/components/ui/` — только shadcn-компоненты, не кастомные - `src/components/.tsx` — переиспользуемые компоненты - `src/api/.ts` — queries/mutations - `src/lib/` — утилиты без React ## Правила - Один компонент = один файл. Файл > 250 строк — декомпозировать - Никаких `useEffect` для fetch — только TanStack Query - Никаких `any`, никаких `@ts-ignore` - Формы — только React Hook Form, never useState для полей - Никаких inline-стилей. Tailwind или css-модуль

API-слой на TanStack Query

// src/api/users.ts import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { z } from "zod"; const UserSchema = z.object({ id: z.number(), email: z.string().email(), name: z.string(), }); export type User = z.infer; export const userKeys = { all: ["users"] as const, me: () => [...userKeys.all, "me"] as const, }; export function useMe() { return useQuery({ queryKey: userKeys.me(), queryFn: async () => { const res = await fetch("/api/v1/users/me", { credentials: "include" }); if (!res.ok) throw new Error("Not authenticated"); return UserSchema.parse(await res.json()); }, }); } export function useLogin() { const qc = useQueryClient(); return useMutation({ mutationFn: async (creds: { email: string; password: string }) => { const res = await fetch("/api/v1/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(creds), }); if (!res.ok) throw new Error("Invalid credentials"); return res.json(); }, onSuccess: () => qc.invalidateQueries({ queryKey: userKeys.all }), }); }

Форма через React Hook Form + Zod

// src/features/login/LoginForm.tsx import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { useLogin } from "@/api/users"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; const schema = z.object({ email: z.string().email("Некорректный email"), password: z.string().min(8, "Минимум 8 символов"), }); type FormData = z.infer; export function LoginForm() { const login = useLogin(); const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(schema), }); return (

login.mutate(data))} className="space-y-4 max-w-sm"> {errors.email &&

{errors.email.message}

} {errors.password &&

{errors.password.message}

} {login.isPending ? "Вхожу..." : "Войти"} {login.isError &&

{login.error.message}

} ); }

Тесты компонента

// src/features/login/LoginForm.test.tsx import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, it, expect } from "vitest"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { LoginForm } from "./LoginForm"; function renderWithQuery(ui: React.ReactNode) { const qc = new QueryClient(); return render({ui}); } describe("LoginForm", () => { it("показывает ошибку на невалидный email", async () => { renderWithQuery(); await userEvent.type(screen.getByPlaceholderText("email"), "not-an-email"); await userEvent.click(screen.getByRole("button")); expect(await screen.findByText(/Некорректный email/)).toBeInTheDocument(); }); });

Подводные камни

  • Claude любит класть всё в один App.tsx. Жёстко требуй разнесения по features/.
  • useEffect(() => fetch(...)) — даже когда запрещено, модель срывается. Если видишь — заверни в TanStack Query.
  • shadcn-компоненты редактируют сами себя. Помни: они живут в твоём проекте, не в node_modules. Не трогай без необходимости.

Как попробовать

1. npm create vite@latest 2. Положи CLAUDE.md 3. Попроси первую фичу — «форма логина с валидацией» 4. Прогони тесты через npm run test 5. Деплой на Vercel: vercel → готово

React-проект от идеи до деплоя через Claude Code

Канал с гайдами и контентом по claude code, выкладываем новости (когда режут лимиты в 10 раз) и какие инструменты через claude реализуем для проектов, канал: https://t.me/claudedevolper

Начать дискуссию