Refactor project structure to use pnpm monorepo; update Dockerfile and related configurations for frontend build process. Adjust .dockerignore and .gitignore to reflect new paths. Modify .env.example for cron job timing. Update CONTRIBUTING.md and README.md for new development instructions.
Build, Test, and Push CFDM Docker Image / test (push) Failing after 3h13m2s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been cancelled

This commit is contained in:
Denozordec
2026-06-15 15:37:36 +07:00
parent 87789fa82d
commit b1467575c5
803 changed files with 16646 additions and 8251 deletions
+44
View File
@@ -0,0 +1,44 @@
---
description: Backend API — при изменениях, затрагивающих UI, строго следовать shadcn Components/Blocks
globs: backend/**/*
alwaysApply: false
---
# Backend API + shadcn/ui
Rust backend: `backend/src/` (Axum, sqlx). Frontend потребляет API через TanStack Query.
## Когда правило активно
Любое изменение в `backend/src/api/handlers/`, DTO, полей ответа, которые отображаются в UI.
## Обязательный порядок
1. **Backend** — handler, валидация, тесты API
2. **Схемы frontend** — `apps/web/src/lib/schemas.ts`, `apps/web/src/queries/index.ts`
3. **UI** — **только** [shadcn Components](https://ui.shadcn.com/docs/components) и [Blocks](https://ui.shadcn.com/blocks)
## Запрещено на frontend при доработке API
- Новые raw `<table>` / `<select>` / кастомные badge-цвета
- Кастомный CSS для отображения новых полей
- Самописные формы без `Field` + RHF + Zod
## Рекомендуемые shadcn-паттерны для типовых API
| API-данные | UI (из docs) |
|------------|--------------|
| Список сущностей | `Table` в `DataTableCard` или `data-table` block |
| Создание записи | `Card` + `FieldGroup` + RHF |
| Enum/фильтр | `Select` |
| Статус | `StatusBadge` → shadcn `Badge` variants |
| Ошибка мутации | `sonner` `toast.error` |
| Пустой список | `Empty` |
| Сводка/метрики | `Card` section-cards ([dashboard-01](https://ui.shadcn.com/blocks)) |
## Согласованность
- Имена полей JSON — camelCase или snake_case как в существующем API; типы в Zod должны совпадать
- Новый endpoint → `queryOptions` factory в `apps/web/src/queries/`, не inline в route
Главное правило frontend: [`frontend-shadcn.mdc`](frontend-shadcn.mdc) · monorepo: [`frontend-monorepo.mdc`](frontend-monorepo.mdc) · обзор: [`shadcn-ui-production.mdc`](shadcn-ui-production.mdc)
@@ -1,56 +0,0 @@
---
description: "Cursor rules for code development with guidelines integration."
globs: **/*
alwaysApply: false
---
1. **Verify Information**: Always verify information before presenting it. Do not make assumptions or speculate without clear evidence.
2. **File-by-File Changes**: Make changes file by file and give me a chance to spot mistakes.
3. **No Apologies**: Never use apologies.
4. **No Understanding Feedback**: Avoid giving feedback about understanding in comments or documentation.
5. **No Whitespace Suggestions**: Don't suggest whitespace changes.
6. **No Summaries**: Don't summarize changes made.
7. **No Inventions**: Don't invent changes other than what's explicitly requested.
8. **No Unnecessary Confirmations**: Don't ask for confirmation of information already provided in the context.
9. **Preserve Existing Code**: Don't remove unrelated code or functionalities. Pay attention to preserving existing structures.
10. **Single Chunk Edits**: Provide all edits in a single chunk instead of multiple-step instructions or explanations for the same file.
11. **No Implementation Checks**: Don't ask the user to verify implementations that are visible in the provided context.
12. **No Unnecessary Updates**: Don't suggest updates or changes to files when there are no actual modifications needed.
13. **Provide Real File Links**: Always provide links to the real files, not the context generated file.
14. **No Current Implementation**: Don't show or discuss the current implementation unless specifically requested.
15. **Check Context Generated File Content**: Remember to check the context generated file for the current file contents and implementations.
16. **Use Explicit Variable Names**: Prefer descriptive, explicit variable names over short, ambiguous ones to enhance code readability.
17. **Follow Consistent Coding Style**: Adhere to the existing coding style in the project for consistency.
18. **Prioritize Performance**: When suggesting changes, consider and prioritize code performance where applicable.
19. **Security-First Approach**: Always consider security implications when modifying or suggesting code changes.
20. **Test Coverage**: Suggest or include appropriate unit tests for new or modified code.
21. **Error Handling**: Implement robust error handling and logging where necessary.
22. **Modular Design**: Encourage modular design principles to improve code maintainability and reusability.
23. **Version Compatibility**: Ensure suggested changes are compatible with the project's specified language or framework versions.
24. **Avoid Magic Numbers**: Replace hardcoded values with named constants to improve code clarity and maintainability.
25. **Consider Edge Cases**: When implementing logic, always consider and handle potential edge cases.
26. **Use Assertions**: Include assertions wherever possible to validate assumptions and catch potential errors early.
-49
View File
@@ -1,49 +0,0 @@
---
description: Code Quality Guidelines
globs: **/*
alwaysApply: false
---
# Code Quality Guidelines
## Verify Information
Always verify information before presenting it. Do not make assumptions or speculate without clear evidence.
## File-by-File Changes
Make changes file by file and give me a chance to spot mistakes.
## No Apologies
Never use apologies.
## No Understanding Feedback
Avoid giving feedback about understanding in comments or documentation.
## No Whitespace Suggestions
Don't suggest whitespace changes.
## No Summaries
Don't summarize changes made.
## No Inventions
Don't invent changes other than what's explicitly requested.
## No Unnecessary Confirmations
Don't ask for confirmation of information already provided in the context.
## Preserve Existing Code
Don't remove unrelated code or functionalities. Pay attention to preserving existing structures.
## Single Chunk Edits
Provide all edits in a single chunk instead of multiple-step instructions or explanations for the same file.
## No Implementation Checks
Don't ask the user to verify implementations that are visible in the provided context.
## No Unnecessary Updates
Don't suggest updates or changes to files when there are no actual modifications needed.
## Provide Real File Links
Always provide links to the real files, not x.md.
## No Current Implementation
Don't show or discuss the current implementation unless specifically requested.
-48
View File
@@ -1,48 +0,0 @@
---
description: Code Quality Guidelines
globs: ["**/*"]
alwaysApply: false
---
# Code Quality Guidelines
## Verify Information
Always verify information before presenting it. Do not make assumptions or speculate without clear evidence.
## File-by-File Changes
Make changes file by file and give me a chance to spot mistakes.
## No Apologies
Never use apologies.
## No Understanding Feedback
Avoid giving feedback about understanding in comments or documentation.
## No Whitespace Suggestions
Don't suggest whitespace changes.
## No Summaries
Don't summarize changes made.
## No Inventions
Don't invent changes other than what's explicitly requested.
## No Unnecessary Confirmations
Don't ask for confirmation of information already provided in the context.
## Preserve Existing Code
Don't remove unrelated code or functionalities. Pay attention to preserving existing structures.
## Single Chunk Edits
Provide all edits in a single chunk instead of multiple-step instructions or explanations for the same file.
## No Implementation Checks
Don't ask the user to verify implementations that are visible in the provided context.
## No Unnecessary Updates
Don't suggest updates or changes to files when there are no actual modifications needed.
## Provide Real File Links
Always provide links to the real files, not x.md.
## No Current Implementation
Don't show or discuss the current implementation unless specifically requested.
+36
View File
@@ -0,0 +1,36 @@
---
description: Conventional commits на русском языке
globs: "**/*"
alwaysApply: false
---
# Commit messages (русский)
Формат: `<type>[optional scope]: <описание>`
## Типы
- `feat` — только новая UX-фича для пользователя
- `fix` — исправление бага
- `chore` — конфиг, зависимости, правила, CI
- `refactor` — рефакторинг без изменения поведения
- `docs` — документация
- `test` — тесты
- `perf` — производительность
## Правила
- Subject в **императиве**, без точки в конце
- Subject и body — **на русском**
- Body (опционально) — что и зачем, не как
- Scope в скобках при необходимости: `feat(domains): добавить фильтр по статусу`
## Примеры
```
fix(frontend): заменить raw table на shadcn Table на странице доменов
feat(certificates): добавить предупреждение об истечении срока
chore(rules): консолидировать правила shadcn/ui для Cursor
```
@@ -1,24 +0,0 @@
---
description: "Cursor rules for Cursor AI development with React, TypeScript, and shadcn/ui integration."
globs: **/*
alwaysApply: false
---
You are an expert AI programming assistant that primarily focuses on producing clear, readable React and TypeScript code.
You always use the latest stable version of TypeScript, JavaScript, React, Node.js, Next.js App Router, Shadcn UI, Tailwind CSS and you are familiar with the latest features and best practices.
You carefully provide accurate, factual, thoughtful answers, and are a genius at reasoning AI to chat, to generate code.
Style and Structure
Naming Conventions
TypeScript Usage
UI and Styling
Performance Optimization
Other Rules need to follow:
Don't be lazy, write all the code to implement features I ask for.
-50
View File
@@ -1,50 +0,0 @@
---
alwaysApply: true
---
You are a Senior Front-End Developer and an Expert in ReactJS, NextJS, JavaScript, TypeScript, HTML, CSS and modern UI/UX frameworks (e.g., TailwindCSS, Shadcn, Radix). You are thoughtful, give nuanced answers, and are brilliant at reasoning. You carefully provide accurate, factual, thoughtful answers, and are a genius at reasoning.
- Follow the users requirements carefully & to the letter.
- First think step-by-step - describe your plan for what to build in pseudocode, written out in great detail.
- Confirm, then write code!
- Always write correct, best practice, DRY principle (Dont Repeat Yourself), bug free, fully functional and working code also it should be aligned to listed rules down below at Code Implementation Guidelines .
- Focus on easy and readability code, over being performant.
- Fully implement all requested functionality.
- Leave NO todos, placeholders or missing pieces.
- Ensure code is complete! Verify thoroughly finalised.
- Include all required imports, and ensure proper naming of key components.
- Be concise Minimize any other prose.
- If you think there might not be a correct answer, you say so.
- If you do not know the answer, say so, instead of guessing.
### Coding Environment
The user asks questions about the following coding languages:
- ReactJS
- NextJS
- JavaScript
- TypeScript
- TailwindCSS
- HTML
- CSS
### Code Implementation Guidelines
Follow these rules when you write code:
- Use early returns whenever possible to make the code more readable.
- Always use Tailwind classes for styling HTML elements; avoid using CSS or tags.
- Use “class:” instead of the tertiary operator in class tags whenever possible.
- Use descriptive variable and function/const names. Also, event functions should be named with a “handle” prefix, like “handleClick” for onClick and “handleKeyDown” for onKeyDown.
- Implement accessibility features on elements. For example, a tag should have a tabindex=“0”, aria-label, on:click, and on:keydown, and similar attributes.
- Use consts instead of functions, for example, “const toggle = () =>”. Also, define a type if possible.
- Don't use semicolons.
### Generate Commit Guidelines
- The commit contains the following structural elements, to communicate intent to the consumers of your library:
- fix: a commit of the type `fix` patches a bug in your codebase (this correlates with PATCH in semantic versioning).
- feat: a commit of the type `feat` introduces a new feature to the codebase (this correlates with MINOR in semantic versioning).
- Others: commit types other than `fix:` and `feat:` are allowed, for example `chore:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, and others.
- A scope may be provided to a commits type, to provide additional contextual information and is contained within parenthesis, e.g., `feat(parser): add ability to parse arrays`.
- Commit messages should be written in the following format:
- Do not end the subject line with a period.
- Use the imperative mood in the subject line.
- Use the body to explain what and why you have done something. In most cases, you can leave out details about how a change has been made.
- The commit message should be structured as follows: `<type>[optional scope]: <description>`
+102
View File
@@ -0,0 +1,102 @@
---
description: shadcn/ui Monorepo — структура apps/web + packages/ui, CLI workflow, импорты @cfdm/ui
globs: apps/web/**/*,packages/ui/**/*
alwaysApply: false
---
# Frontend Monorepo (shadcn/ui)
**Обязательный стандарт структуры** — [Monorepo docs](https://ui.shadcn.com/docs/monorepo).
## Layout
```
apps/web/ # Vite SPA (routes, queries, domain components)
packages/ui/ # @cfdm/ui — shadcn primitives, utils, hooks, globals.css
```
`backend/` — Rust, **вне** npm workspaces.
## Два components.json
| Файл | Назначение |
|------|------------|
| [`apps/web/components.json`](apps/web/components.json) | App aliases; `ui` → `@cfdm/ui/components` |
| [`packages/ui/components.json`](packages/ui/components.json) | UI package aliases |
**Синхронизировать:** `style`, `iconLibrary`, `baseColor` в обоих файлах.
## CLI — только из apps/web
```bash
cd apps/web
pnpm dlx shadcn@latest docs button
pnpm dlx shadcn@latest add button
pnpm dlx shadcn@latest add sidebar-07
pnpm dlx shadcn@latest add login-03
pnpm dlx shadcn@latest apply b2fA --only theme -y
```
Перед обновлением существующих компонентов:
```bash
pnpm dlx shadcn@latest add button --dry-run
pnpm dlx shadcn@latest add button --diff
pnpm dlx shadcn@latest info --json
```
## Куда CLI кладёт файлы
| Команда | Куда |
|---------|------|
| `add button` | `packages/ui/src/components/button.tsx` |
| `add login-03` | примитивы → `packages/ui`, block → `apps/web/src/components/` |
## Импорты
```tsx
import { Button } from '@cfdm/ui/components/button'
import { cn } from '@cfdm/ui/lib/utils'
import { useIsMobile } from '@cfdm/ui/hooks/use-mobile'
import '@cfdm/ui/globals.css' // только в main.tsx
```
| Запрещено | Разрешено |
|-----------|-----------|
| `@/components/ui/*` | `@cfdm/ui/components/*` |
| `apps/web/src/components/ui/` | `packages/ui/src/components/` |
| Ручное редактирование `globals.css` | `pnpm dlx shadcn@latest apply b2fA --only theme` |
Community registry: переписывать импорты на `@cfdm/ui/...`.
## Разделение ответственности
- **`packages/ui`** — только output `shadcn add` (примитивы, registry hooks, `cn`)
- **`apps/web/src/components`** — blocks, layout, domain (`login-form`, `app-shell`, `PageHeader`)
## Стили (Tailwind v4 monorepo)
`packages/ui/src/styles/globals.css` — единственный CSS-файл. **Обязательно** `@source` для обоих workspace:
```css
@source "../"; /* packages/ui/src */
@source "../../../apps/web/src"; /* apps/web/src */
```
Без `@source` Tailwind не видит классы из `packages/ui` и `apps/web` — UI ломается (нет sidebar, card, и т.д.).
```bash
pnpm install
pnpm --filter web dev
pnpm --filter web build
```
## Чеклист
- [ ] Два `components.json` согласованы
- [ ] `shadcn add` из `apps/web`
- [ ] UI-импорты через `@cfdm/ui/components/*`
- [ ] Нет `apps/web/src/components/ui/`
- [ ] `pnpm --filter web build` без ошибок
См. также: [`frontend-shadcn.mdc`](frontend-shadcn.mdc), [`vite-tanstack-frontend.mdc`](vite-tanstack-frontend.mdc).
+153
View File
@@ -0,0 +1,153 @@
---
description: Frontend — ТОЛЬКО shadcn/ui docs (Components, Blocks, Installation); best practices, CLI-first
globs: apps/web/**/*,packages/ui/**/*
alwaysApply: false
---
# Frontend — shadcn/ui (обязательно)
**Источник истины — только официальная документация.** Не выдумывать UI, не писать кастомный CSS, не обходить CLI.
Monorepo layout — [`frontend-monorepo.mdc`](frontend-monorepo.mdc).
| Документ | URL |
|----------|-----|
| **Components** | https://ui.shadcn.com/docs/components |
| **Blocks** | https://ui.shadcn.com/blocks |
| **Installation** | https://ui.shadcn.com/docs/installation |
| **Monorepo** | https://ui.shadcn.com/docs/monorepo |
| **Theming** | https://ui.shadcn.com/docs/theming |
| **Dark Mode** | https://ui.shadcn.com/docs/dark-mode |
| **Forms (RHF)** | https://ui.shadcn.com/docs/forms/react-hook-form |
## Шаг 0 — перед любым UI-кодом
1. Открыть **Components** или **Blocks** — найти готовое решение
2. `cd apps/web && pnpm dlx shadcn@latest docs <component>` — API и примеры
3. `cd apps/web && pnpm dlx shadcn@latest search "<query>"` — если компонент неочевиден
4. Только потом писать код
**Новая страница** → сначала [Blocks](https://ui.shadcn.com/blocks), потом `pnpm dlx shadcn@latest add <block-id>`.
## Шаг 1 — CLI (обязательно)
```bash
cd apps/web
pnpm dlx shadcn@latest add table select badge card field input button ...
pnpm dlx shadcn@latest add sidebar-07 # layout
pnpm dlx shadcn@latest add dashboard-01 # dashboard
pnpm dlx shadcn@latest add login-03 # auth
pnpm dlx shadcn@latest apply b2fA --only theme -y # тема — ТОЛЬКО так
```
- Копипаст с сайта **без** CLI — запрещено
- `packages/ui/src/components/*` — только registry; domain-логика → `apps/web/src/components/<name>.tsx`
## Шаг 2 — композиция (best practices)
### Приоритет
1. Установленный `@cfdm/ui/components/*`
2. Block из registry (адаптация под TanStack Router)
3. Shared проекта: `PageHeader`, `StatusBadge`, `ResourceList`, `DataTableCard`
4. Domain-обёртка — последний уровень кастомизации
### Запрещено
| ❌ | ✅ из [Components](https://ui.shadcn.com/docs/components) |
|----|-----------------------------------------------------------|
| `<table>`, `<select>`, `<hr>` | `Table`, `Select`, `Separator` |
| `bg-emerald-*`, `text-blue-500`, hex в className | `bg-primary`, `text-muted-foreground`, `Badge variant` |
| Ручной `globals.css`, `.css` модули | CLI `apply b2fA --only theme` |
| `space-y-*` / `space-x-*` | `flex` + `gap-*` |
| `w-10 h-10` | `size-10` |
| `className` для цветов Button/Badge | `variant`, `size` |
| `useState` для полей формы | `FieldGroup` + RHF + Zod |
| Styled `<Link>` | `Button variant="link"` + `render={<Link />}` |
| `inline style={{}}` в routes | layout Tailwind |
| `animate-pulse` div | `Skeleton` |
| кастомный toast | `sonner` → `toast()` |
| `@/components/ui/*` | `@cfdm/ui/components/*` |
### Формы
По https://ui.shadcn.com/docs/forms/react-hook-form:
```tsx
<FieldGroup>
<Field data-invalid={!!errors.name}>
<FieldLabel htmlFor="name">Имя</FieldLabel>
<Input id="name" aria-invalid={!!errors.name} {...register('name')} />
</Field>
</FieldGroup>
```
### Card
`CardHeader` / `CardTitle` / `CardDescription` / `CardContent` / `CardFooter` — полная композиция из docs.
### Таблицы
`Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell` — из docs.
Сложная таблица → [Data Table](https://ui.shadcn.com/docs/components/data-table) + block `dashboard-01`.
### Графики
`Chart` + `ChartContainer` + `chartConfig` с `var(--chart-1)` — не raw recharts без обёртки.
### Иконки в Button
```tsx
<Button>
<PlusIcon data-icon="inline-start" />
Создать
</Button>
```
Без `size-4` на иконке внутри shadcn-компонента.
## Стек (не shadcn, но обязателен)
TanStack Router + Query — [`vite-tanstack-frontend.mdc`](vite-tanstack-frontend.mdc).
- Preset: **base-nova** + **neutral** — [`apps/web/components.json`](apps/web/components.json), [`packages/ui/components.json`](packages/ui/components.json)
- `@base-ui/react` → `render` prop (не Radix `asChild`)
- **Не Next.js** — нет Server Components, `'use client'`
## Эталоны проекта
| Зона | Файл | Block |
|------|------|-------|
| Shell | `apps/web/src/components/layout/app-shell.tsx` | [sidebar-07](https://ui.shadcn.com/blocks) |
| Login | `apps/web/src/routes/login.tsx` | [login-03](https://ui.shadcn.com/blocks) |
| Dashboard | `apps/web/src/routes/_auth/index.tsx` | [dashboard-01](https://ui.shadcn.com/blocks) |
| CRUD | `routes/_auth/services.tsx`, `domains/index.tsx` | Card + Field + Table |
## Структура файлов
```
apps/web/src/
components/ ← domain + layout + shared (blocks)
routes/ ← страницы (композиция @cfdm/ui, без raw HTML)
queries/ ← queryOptions (не inline в routes)
lib/schemas.ts ← Zod для форм
packages/ui/src/
components/ ← только CLI (не трогать под кейс)
hooks/ ← registry hooks (use-mobile)
lib/utils.ts ← cn()
styles/globals.css ← только output shadcn CLI
```
## Чеклист перед завершением задачи
- [ ] Решение есть в https://ui.shadcn.com/docs/components или /blocks
- [ ] Компоненты добавлены через `pnpm dlx shadcn@latest add` из `apps/web`
- [ ] Нет кастомного CSS и raw HTML-примитивов
- [ ] Semantic tokens, `variant`/`size` вместо переопределения className
- [ ] UI-импорты через `@cfdm/ui/components/*`
- [ ] `pnpm --filter web build` без ошибок
## Язык
Ответы пользователю — русский. Commits — [`commit-messages-ru.mdc`](commit-messages-ru.mdc).
@@ -1,56 +0,0 @@
---
description: Cursor rules for TypeScript, React, Node.js, clean architecture, testing, and WHY-oriented engineering guidance.
globs: **/*
alwaysApply: false
---
# Full-Stack Engineering Standards
You are a senior full-stack developer specializing in TypeScript, React, and Node.js.
Every rule includes a WHY explanation for the reasoning behind it.
## Coding Standards
- Use strict TypeScript. Never use `any`. Use `unknown` for dynamic data.
> WHY: Type safety prevents runtime errors and improves developer experience.
- Max function length: 20 lines. Extract helpers for complex logic.
> WHY: Improves testability, readability, and makes code review easier.
- Naming: camelCase for variables/functions, PascalCase for classes/interfaces, UPPER_SNAKE for constants.
> WHY: Consistent with TypeScript ecosystem standards.
- Prefer interfaces over type aliases for objects.
> WHY: Interfaces are extendable and produce better error messages.
## Architecture
- Clean Architecture with dependency inversion. Domain layer is framework-agnostic.
> WHY: Testable business logic that survives framework changes.
- Repository pattern for data access. Never call ORM directly from business logic.
> WHY: Decouples persistence from domain, enables testing with in-memory implementations.
- React Query for server state, Zustand for client state. No Redux.
> WHY: Lighter weight, better TypeScript support, less boilerplate.
## Error Handling
- Custom AppError hierarchy with HTTP status codes. Throw for exceptional, return Result for expected failures.
> WHY: Clear intent — callers know which errors to catch vs handle.
- Structured logging with Winston. Never log sensitive data (passwords, tokens, PII).
> WHY: Observability without security risk. Structured logs enable alerting.
## Testing
- 80% unit coverage, 100% critical paths. Use factory functions for test data.
> WHY: Factory functions are maintainable and composable. Fixtures become stale.
- Mock only external dependencies (APIs, DB). Never mock internal logic.
> WHY: Tests should reflect reality. Over-mocking hides real bugs.
## Security
- Validate all input with Zod schemas at API boundaries.
> WHY: Runtime validation catches what TypeScript can't — malformed external data.
- Rate limit all public endpoints. Use helmet middleware.
> WHY: Defense in depth against abuse and common web vulnerabilities.
## Git
- Max 400 lines per PR. Conventional commits: feat/fix/refactor/test/docs.
> WHY: Small PRs get reviewed faster and have fewer bugs.
@@ -1,40 +0,0 @@
---
description: "Cursor rules for React component creation and development."
globs: **/*
alwaysApply: false
---
# Cursor Rules
## Whenever you need a React component
1. Carefully consider the component's purpose, functionality, and design
2. Think slowly, step by step, and outline your reasoning
3. Check if a similar component already exists in any of the following locations
1. packages/ui/src/components
2. apps/spa/src/components
4. If it doesn't exist, generate a detailed prompt for the component, including:
- Component name and purpose
- Desired props and their types
- Any specific styling or behavior requirements
- Mention of using Tailwind CSS for styling
- Request for TypeScript usage
5. URL encode the prompt.
6. Create a clickable link in this format:
[ComponentName](https://v0.dev/chat?q={encoded_prompt})
7. After generating, adapt the component to fit our project structure:
- Import
- common shadcn/ui components from <ui_package_alias>@repo/ui/components/ui/</ui_package_alias>
- app specific components from <app_package_alias>@/components</app_package_alias>
- Ensure it follows our existing component patterns
- Add any necessary custom logic or state management
Example prompt template:
"Create a React component named {ComponentName} using TypeScript and Tailwind CSS. It should {description of functionality}. Props should include {list of props with types}. The component should {any specific styling or behavior notes}. Please provide the full component code."
Remember to replace placeholders like <ui_package_path> and <app_package_alias> with the actual values used in your project.
@@ -1,273 +0,0 @@
---
description: "Cursor rules for React SPAs combining TanStack Router v1 and TanStack Query v5 for zero-loading-spinner routing and type-safe server state."
globs: **/*
alwaysApply: false
---
You are an expert in React, TanStack Router v1, TanStack Query v5, TypeScript, Vite, and building fully type-safe single-page applications.
# React + TanStack Router + TanStack Query Guidelines
## Architecture Overview
- TanStack Router handles all routing, URL state, and navigation
- TanStack Query manages all server state, caching, and async data
- React components are pure UI — they read from Query cache and trigger mutations
- Loaders bridge Router and Query: they prefetch into the Query cache before render
- This eliminates loading spinners for route-level data; Suspense handles component-level loading
## Project Setup
```
src/
routes/
__root.tsx
index.tsx
posts/
index.tsx
$postId.tsx
queries/ ← Query definitions (queryOptions factories)
posts.ts
users.ts
api/ ← API client functions (fetchers)
posts.ts
users.ts
lib/
queryClient.ts
router.ts
main.tsx
```
## QueryClient + Router Setup
```ts
// src/lib/queryClient.ts
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60,
retry: (count, error: any) => error?.status !== 404 && count < 2,
},
},
})
```
```tsx
// src/lib/router.ts
import { createRouter } from '@tanstack/react-router'
import { routeTree } from '../routeTree.gen'
import { queryClient } from './queryClient'
export const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: 'intent',
defaultPreloadStaleTime: 0,
})
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
```
```tsx
// src/main.tsx
import { RouterProvider } from '@tanstack/react-router'
import { QueryClientProvider } from '@tanstack/react-query'
import { router } from './lib/router'
import { queryClient } from './lib/queryClient'
ReactDOM.createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} context={{ queryClient }} />
</QueryClientProvider>
)
```
## Query Definitions (queryOptions factories)
- Co-locate query key, fetcher, and staleTime in one place
- Share between Router loaders and component hooks
```ts
// src/queries/posts.ts
import { queryOptions, infiniteQueryOptions } from '@tanstack/react-query'
import { fetchPost, fetchPosts } from '../api/posts'
export const postKeys = {
all: ['posts'] as const,
lists: () => [...postKeys.all, 'list'] as const,
list: (filters?: PostFilters) => [...postKeys.lists(), filters] as const,
details: () => [...postKeys.all, 'detail'] as const,
detail: (id: string) => [...postKeys.details(), id] as const,
}
export const postDetailQueryOptions = (id: string) =>
queryOptions({
queryKey: postKeys.detail(id),
queryFn: () => fetchPost(id),
staleTime: 1000 * 60 * 5,
})
export const postsListQueryOptions = (filters?: PostFilters) =>
queryOptions({
queryKey: postKeys.list(filters),
queryFn: () => fetchPosts(filters),
staleTime: 1000 * 60,
})
```
## Router Loader + Query Integration
- Loaders call `queryClient.ensureQueryData` — populates cache, renders immediately without spinner
- Components then call `useQuery` with the same options — reads from cache synchronously
```tsx
// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
import { useQuery } from '@tanstack/react-query'
import { postDetailQueryOptions } from '../../queries/posts'
export const Route = createFileRoute('/posts/$postId')({
loader: ({ context: { queryClient }, params }) =>
queryClient.ensureQueryData(postDetailQueryOptions(params.postId)),
errorComponent: ({ error }) => <ErrorMessage error={error} />,
pendingComponent: PostSkeleton,
component: PostDetail,
})
function PostDetail() {
const { postId } = Route.useParams()
// data is already in cache from loader — no loading state
const { data: post } = useQuery(postDetailQueryOptions(postId))
return <article><h1>{post!.title}</h1></article>
}
```
## Search Params + Query Integration
- Use TanStack Router search params as the source of truth for filter/pagination state
- Pass search params into queryOptions to drive query key and fetcher
```tsx
// src/routes/posts/index.tsx
import { createFileRoute, Link } from '@tanstack/react-router'
import { useQuery } from '@tanstack/react-query'
import { z } from 'zod'
import { postsListQueryOptions } from '../../queries/posts'
const searchSchema = z.object({
page: z.number().int().min(1).default(1),
category: z.string().optional(),
})
export const Route = createFileRoute('/posts/')({
validateSearch: searchSchema,
loader: ({ context: { queryClient }, location: { search } }) =>
queryClient.ensureQueryData(postsListQueryOptions(search)),
component: PostsList,
})
function PostsList() {
const search = Route.useSearch()
const navigate = Route.useNavigate()
const { data: posts } = useQuery(postsListQueryOptions(search))
return (
<div>
{posts?.map(post => (
<Link key={post.id} to="/posts/$postId" params={{ postId: post.id }}>
{post.title}
</Link>
))}
<button onClick={() => navigate({ search: { ...search, page: search.page + 1 } })}>
Next Page
</button>
</div>
)
}
```
## Mutations
```tsx
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useNavigate } from '@tanstack/react-router'
import { postKeys } from '../../queries/posts'
function CreatePostForm() {
const queryClient = useQueryClient()
const navigate = useNavigate()
const mutation = useMutation({
mutationFn: createPost,
onSuccess: (newPost) => {
// Populate detail cache immediately
queryClient.setQueryData(postKeys.detail(newPost.id), newPost)
// Invalidate list queries
queryClient.invalidateQueries({ queryKey: postKeys.lists() })
// Navigate to new post (no loading — cache is warm)
navigate({ to: '/posts/$postId', params: { postId: newPost.id } })
},
})
return (/* form JSX */)
}
```
## Authentication Pattern
```tsx
// src/routes/__root.tsx
import { createRootRouteWithContext } from '@tanstack/react-router'
export interface RouterContext {
queryClient: QueryClient
auth: { isAuthenticated: boolean; user: User | null }
}
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootLayout,
})
// src/routes/_auth.tsx (pathless layout for protected routes)
export const Route = createFileRoute('/_auth')({
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login', search: { redirect: location.pathname } })
}
},
})
```
## Prefetching on Hover
```tsx
function PostCard({ post }: { post: Post }) {
const queryClient = useQueryClient()
return (
<Link
to="/posts/$postId"
params={{ postId: post.id }}
onMouseEnter={() => queryClient.prefetchQuery(postDetailQueryOptions(post.id))}
>
{post.title}
</Link>
)
}
```
## DevTools (Development Only)
```tsx
// In __root.tsx
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
// Inside component
{import.meta.env.DEV && (
<>
<TanStackRouterDevtools position="bottom-left" />
<ReactQueryDevtools buttonPosition="bottom-right" />
</>
)}
```
## Key Rules
- Always define `queryOptions` outside of components — not inline in `useQuery()`
- Never use `useEffect` to fetch data — use loaders or `useQuery`
- Always type router context — `declare module '@tanstack/react-router'` registration is required
- Search params are the only source of truth for URL-driven filter state
- Mutations should `setQueryData` + `invalidateQueries`, not just invalidate, for instant UI feedback
@@ -1,116 +0,0 @@
---
description: "React SPA with TanStack Router v1 + TanStack Query v5 — the definitive pattern for zero-loading-spinner routing, type-safe URLs, and cache-first data"
globs: ["src/routes/**/*", "src/queries/**/*", "src/lib/router.ts", "src/lib/queryClient.ts"]
alwaysApply: false
---
You are an expert in React, TanStack Router v1, TanStack Query v5, TypeScript, and Vite.
## Architecture
- TanStack Router: routing, URL state, navigation
- TanStack Query: server state, caching, mutations
- Loader = bridge: prefetches into Query cache before render → zero loading spinners for route data
- Components are pure UI: read from Query cache, trigger mutations
## Setup
```ts
// src/lib/queryClient.ts
export const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 60_000 } },
})
// src/lib/router.ts
export const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: 'intent',
defaultPreloadStaleTime: 0,
})
declare module '@tanstack/react-router' {
interface Register { router: typeof router }
}
// src/main.tsx
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} context={{ queryClient }} />
</QueryClientProvider>
```
## Query Definitions
```ts
// src/queries/posts.ts
export const postKeys = {
all: ['posts'] as const,
detail: (id: string) => [...postKeys.all, 'detail', id] as const,
list: (f?: PostFilters) => [...postKeys.all, 'list', f] as const,
}
export const postQueryOptions = (id: string) =>
queryOptions({ queryKey: postKeys.detail(id), queryFn: () => fetchPost(id) })
export const postsQueryOptions = (filters?: PostFilters) =>
queryOptions({ queryKey: postKeys.list(filters), queryFn: () => fetchPosts(filters) })
```
## Loader + Component (zero loading state)
```tsx
export const Route = createFileRoute('/posts/$postId')({
loader: ({ context: { queryClient }, params }) =>
queryClient.ensureQueryData(postQueryOptions(params.postId)),
component: PostDetail,
})
function PostDetail() {
const { postId } = Route.useParams()
const { data: post } = useQuery(postQueryOptions(postId)) // always in cache from loader
return <h1>{post!.title}</h1>
}
```
## Search Params → Query Key
```tsx
const searchSchema = z.object({ page: z.number().default(1), q: z.string().optional() })
export const Route = createFileRoute('/posts/')({
validateSearch: searchSchema,
loader: ({ context: { queryClient }, location: { search } }) =>
queryClient.ensureQueryData(postsQueryOptions(search)),
component: PostsList,
})
function PostsList() {
const search = Route.useSearch()
const { data } = useQuery(postsQueryOptions(search))
// ...
}
```
## Mutations
```tsx
const mutation = useMutation({
mutationFn: createPost,
onSuccess: (newPost) => {
queryClient.setQueryData(postKeys.detail(newPost.id), newPost) // warm cache
queryClient.invalidateQueries({ queryKey: postKeys.list() })
navigate({ to: '/posts/$postId', params: { postId: newPost.id } }) // instant — no spinner
},
})
```
## Hover Prefetching
```tsx
<Link
to="/posts/$postId"
params={{ postId: post.id }}
onMouseEnter={() => queryClient.prefetchQuery(postQueryOptions(post.id))}
>
{post.title}
</Link>
```
## Key Rules
- Always define `queryOptions` outside components — never inline inside `useQuery()`
- Never use `useEffect` for data fetching — use loaders or `useQuery`
- Search params are the single source of truth for filter/pagination state
- After mutations: `setQueryData` + `invalidateQueries` for instant UI feedback
- `declare module '@tanstack/react-router'` router registration is required for full type safety
-310
View File
@@ -1,310 +0,0 @@
---
description: Definitive best practices for shadcn/ui — organization, TypeScript, performance, and accessible components.
globs: **/*
alwaysApply: false
---
# shadcn Best Practices
Definitive guidelines for `shadcn/ui` development and integration. Application source lives under `src/`; components under `src/components/`.
## 1. Code Organization and Structure
Organize components to reflect UI hierarchy and promote discoverability.
**Rule:** Place domain-specific components under `src/components/<domain>` and reusable UI primitives under `src/components/ui`. One primary component per file; use kebab-case filenames for UI primitives (shadcn CLI default) and PascalCase for exported component names.
❌ BAD:
```
// src/components/Button.tsx
// src/components/profile-card.tsx
// src/components/user-settings/index.tsx (multiple components in one file)
```
✅ GOOD:
```
// src/components/ui/button.tsx
// src/components/forms/date-picker.tsx
// src/components/layout/sidebar.tsx
// src/components/forms/index.ts
export * from "./date-picker";
export * from "./input";
```
## 2. Component Architecture
Favor functional components, composition, and explicit prop definitions.
**Rule:** Use functional components with `React.forwardRef` and `asChild` for seamless integration with Radix primitives.
❌ BAD:
```tsx
// No ref forwarding, no asChild
const Button = ({ children, onClick }) => (
<button onClick={onClick}>{children}</button>
);
```
✅ GOOD:
```tsx
// src/components/ui/button.tsx
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants };
```
## 3. TypeScript and API Design
Enforce strict TypeScript with clear interfaces and robust validation.
**Rule:** Use interfaces for component props. Validate form data with Zod schemas. Avoid `any` and prefer explicit types.
❌ BAD:
```typescript
// Vague props, no validation
type UserFormProps = {
data: any;
onSubmit: (values: any) => void;
};
```
✅ GOOD:
```typescript
// src/components/forms/user-form.tsx
import { z } from "zod";
export interface UserFormProps {
initialData?: UserFormData;
onSubmit: (values: UserFormData) => void;
}
export const userFormSchema = z.object({
id: z.string().optional(),
name: z.string().min(2, "Name must be at least 2 characters."),
email: z.string().email("Invalid email address."),
});
export type UserFormData = z.infer<typeof userFormSchema>;
```
## 4. Theming and Styling
Leverage Tailwind CSS and `class-variance-authority` (CVA) for consistent, maintainable styling.
**Rule:** Define component variants using CVA. Use the `cn` utility for conditional class merging. Centralize Tailwind configuration and design tokens.
❌ BAD:
```tsx
// Inconsistent inline styles or direct class manipulation
<button className={`p-2 ${isActive ? "bg-blue-500" : "bg-gray-200"}`}>
```
✅ GOOD:
```tsx
// src/components/ui/badge.tsx
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold",
{
variants: {
variant: {
default: "border-transparent bg-primary text-primary-foreground",
secondary: "border-transparent bg-secondary text-secondary-foreground",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };
```
## 5. Common Patterns and Anti-patterns
**Rule:** Use React Hook Form with Zod for all forms. Implement early returns and guard clauses for error handling.
❌ BAD:
```tsx
// Deeply nested logic, manual form state
if (data) {
// ... many lines
if (isValid) {
// ... more lines
}
}
```
✅ GOOD:
```tsx
// Early return for invalid state
if (!user) {
return <p>User not found.</p>;
}
// React Hook Form + Zod example
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { userFormSchema, type UserFormData, type UserFormProps } from "./user-form";
function UserProfileForm({ initialData, onSubmit }: UserFormProps) {
const form = useForm<UserFormData>({
resolver: zodResolver(userFormSchema),
defaultValues: initialData,
});
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Form fields */}
</form>
);
}
```
## 6. Performance Considerations
Optimize for fast initial loads and smooth interactions.
**Rule:** Lazy-load heavy UI sections (dialogs, data tables) via `React.lazy` or dynamic imports. Memoize expensive components and callbacks.
❌ BAD:
```tsx
// Always loads heavy component
import { BigComplexChart } from "./big-complex-chart";
function Dashboard() {
return <BigComplexChart data={...} />;
}
```
✅ GOOD:
```tsx
import React from "react";
const LazyBigComplexChart = React.lazy(() => import("./big-complex-chart"));
function Dashboard() {
const [showChart, setShowChart] = React.useState(false);
const handleToggleChart = React.useCallback(() => {
setShowChart((prev) => !prev);
}, []);
return (
<div>
<Button onClick={handleToggleChart}>Toggle Chart</Button>
{showChart && (
<React.Suspense fallback={<div>Loading chart...</div>}>
<LazyBigComplexChart data={...} />
</React.Suspense>
)}
</div>
);
}
```
## 7. Accessibility
Build inclusive UIs by leveraging Radix primitives and ARIA attributes.
**Rule:** Prefer `shadcn/ui` components (Radix-based) for built-in accessibility. Ensure custom components pass ARIA attributes and manage focus correctly.
❌ BAD:
```tsx
// Custom button without ARIA attributes or proper semantics
<div role="button" onClick={...}>Click me</div>
```
✅ GOOD:
```tsx
import { Button } from "@/components/ui/button";
<Button onClick={() => alert("Action!")}>Perform Action</Button>
```
## 8. Common Pitfalls and Gotchas
**Rule:** Never directly modify `shadcn/ui` component files for one-off styling — extend with `cn` or wrap in higher-level components. Avoid `dangerouslySetInnerHTML` unless content is sanitized.
❌ BAD:
```tsx
// Direct modification of a shadcn component (overwritten by CLI updates)
// src/components/ui/button.tsx (modified for a single use case)
```
```tsx
// Security vulnerability
<div dangerouslySetInnerHTML={{ __html: userProvidedContent }} />
```
✅ GOOD:
```tsx
import { Button } from "@/components/ui/button";
<Button className="bg-red-500 hover:bg-red-600">Custom Red Button</Button>
```
```tsx
import DOMPurify from "dompurify";
const sanitizedContent = DOMPurify.sanitize(userProvidedContent);
return <div className="prose" dangerouslySetInnerHTML={{ __html: sanitizedContent }} />;
// Prefer rendering text directly when possible:
// return <p>{userProvidedContent}</p>;
```
+29
View File
@@ -0,0 +1,29 @@
---
description: shadcn/ui — глобальные UI-принципы проекта; frontend см. frontend-shadcn.mdc
alwaysApply: true
---
# shadcn/ui — правила проекта
UI строится **исключительно** по [shadcn/ui](https://ui.shadcn.com/docs/installation): [Components](https://ui.shadcn.com/docs/components), [Blocks](https://ui.shadcn.com/blocks), [Monorepo](https://ui.shadcn.com/docs/monorepo).
## Разработка frontend
**Все правила frontend** — в [`frontend-shadcn.mdc`](frontend-shadcn.mdc) и [`frontend-monorepo.mdc`](frontend-monorepo.mdc) (globs: `apps/web/**`, `packages/ui/**`).
Кратко: docs → CLI из `apps/web` → Block → композиция → `pnpm --filter web build`. Кастомный CSS и самописные примитивы **запрещены**.
## Стек
- Monorepo: `apps/web` + `packages/ui` (`@cfdm/ui`), pnpm workspaces
- Vite + TanStack Router/Query + shadcn **base-nova**
- Конфиг: [`apps/web/components.json`](apps/web/components.json), [`packages/ui/components.json`](packages/ui/components.json)
- Тема: `pnpm dlx shadcn@latest apply b2fA --only theme -y` — единственный способ менять `packages/ui/src/styles/globals.css`
## Backend → UI
При правках API с экранами: [`backend-api-ui.mdc`](backend-api-ui.mdc).
## Язык
Русский. Commits: [`commit-messages-ru.mdc`](commit-messages-ru.mdc).
@@ -1,64 +0,0 @@
---
description: Cursor rules for Tailwind development with shadcn/ui integration.
globs: **/*
alwaysApply: false
---
# Tailwind + shadcn/ui Development
You are an expert AI programming assistant in VSCode that primarily focuses on producing clear, readable TypeScript Next.js code.
You are thoughtful, give nuanced answers, and are brilliant at reasoning. You carefully provide accurate, factual, thoughtful answers, and are a genius at reasoning.
Follow the user's requirements carefully & to the letter.
First think step-by-step — describe your plan for what to build in pseudocode, written out in great detail.
Confirm, then write code!
Always write correct, up-to-date, bug-free, fully functional and working, secure, performant and efficient code.
Focus on readability over being performant.
Fully implement all requested functionality.
Leave NO todo's, placeholders or missing pieces.
Ensure code is complete! Verify thoroughly finalized.
Include all required imports, and ensure proper naming of key components.
Be concise. Minimize any other prose.
If you think there might not be a correct answer, you say so. If you do not know the answer, say so instead of guessing.
## Tech Stack
- Next.js (App Router), React, TypeScript
- Tailwind CSS for all styling
- shadcn/ui + Radix UI for accessible components
- Application source files live in the `src/` folder
## Tailwind CSS
- Use Tailwind utility classes for styling; avoid custom CSS unless necessary
- Prefer design tokens (`bg-background`, `text-foreground`, `border-border`) over hardcoded colors
- Use `cn()` from `@/lib/utils` to merge conditional classes
- Compose with responsive and state variants (`sm:`, `md:`, `hover:`, `focus-visible:`)
- Keep class lists readable; extract repeated patterns into components
## shadcn/ui
- Prefer existing shadcn components from `@/components/ui/` before building from scratch
- Add new components with the shadcn CLI; do not copy-paste from docs without project setup
- Extend shadcn components via `className` and composition, not by editing primitives unless required
- Use Radix behavior and shadcn styling patterns for forms, dialogs, dropdowns, and toasts
- Wire forms with `react-hook-form` + `zod` when using shadcn form components
## Code Guidelines
- Use early returns for readability
- Prefix event handlers with `handle` (e.g. `handleClick`, `handleSubmit`)
- Prefix booleans with verbs (`isLoading`, `hasError`, `canSubmit`)
- Default to Server Components; use `'use client'` only when needed
- Use semantic HTML and accessible labels, focus states, and keyboard support
@@ -1,396 +0,0 @@
---
description: Definitive guidelines for using TanStack Query (formerly React Query) to manage server state efficiently, ensure type safety, and optimize performance in React applications.
globs: **/*.{js,jsx,ts,tsx}
alwaysApply: false
---
# TanStack Query (react-query) Best Practices
This document outlines the definitive best practices for using TanStack Query in our React applications. Adhering to these guidelines ensures consistent, performant, and maintainable data fetching and state management.
## 1. Query Keys: The Foundation of Caching
**ALWAYS** use stable, descriptive array keys. These are fundamental for caching, refetching, and invalidation. For dynamic data, embed parameters directly into the array.
### ✅ GOOD: Stable Array Keys & Key Factories
```typescript
// 1. Simple, static key
const USERS_KEY = ['users'];
// 2. Dynamic key with parameters
const userKeys = {
all: ['users'] as const,
lists: () => [...userKeys.all, 'list'] as const,
list: (filters: { status?: string; page?: number }) =>
[...userKeys.lists(), { filters }] as const,
details: () => [...userKeys.all, 'detail'] as const,
detail: (id: string) => [...userKeys.details(), id] as const,
};
// Usage:
// useQuery(userKeys.all, fetchAllUsers);
// useQuery(userKeys.list({ status: 'active', page: 1 }), fetchUsers);
// useQuery(userKeys.detail(userId), fetchUserById);
```
### ❌ BAD: Unstable or Generic Keys
```typescript
// String keys are less flexible for dynamic data and filtering
useQuery('users', fetchUsers);
// Anonymous object keys are unstable and break caching
useQuery(['users', { id: userId }], fetchUserById); // Object literal creates new reference each render
```
## 2. Custom Hooks: Encapsulate Logic
**ALWAYS** wrap `useQuery` and `useMutation` calls in custom hooks. This centralizes data fetching logic, improves reusability, enhances type safety, and keeps components clean.
### ✅ GOOD: Dedicated Custom Hooks
```typescript
// hooks/useUsers.ts
import { useQuery } from '@tanstack/react-query';
import { fetchUsers, User } from '../api'; // Assume api.ts defines fetchUsers
const userKeys = {
all: ['users'] as const,
list: (filters: { status?: string }) => [...userKeys.all, { filters }] as const,
};
export function useUsers(filters?: { status?: string }) {
return useQuery<User[], Error>({
queryKey: userKeys.list(filters || {}),
queryFn: () => fetchUsers(filters),
});
}
// components/UserList.tsx
import { useUsers } from '../hooks/useUsers';
function UserList({ statusFilter }: { statusFilter?: string }) {
const { data: users, isLoading, error } = useUsers({ status: statusFilter });
if (isLoading) return <div>Loading users...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{users?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
```
### ❌ BAD: Direct `useQuery` in Components
```typescript
// components/UserList.tsx
import { useQuery } from '@tanstack/react-query';
import { fetchUsers } from '../api';
function UserList({ statusFilter }: { statusFilter?: string }) {
// Logic is duplicated if another component needs users
// Query key is less organized
const { data: users, isLoading, error } = useQuery({
queryKey: ['users', { status: statusFilter }],
queryFn: () => fetchUsers({ status: statusFilter }),
});
// ... rest of component
}
```
## 3. Query Functions: Separate and Stable
**NEVER** pass anonymous functions directly to `queryFn`. **ALWAYS** declare `queryFn` separately to ensure stability, prevent unnecessary re-renders, and improve testability.
### ✅ GOOD: Separated Query Functions
```typescript
// api.ts
export async function fetchUserById(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
}
// hooks/useUser.ts
import { useQuery } from '@tanstack/react-query';
import { fetchUserById } from '../api';
const userKeys = {
detail: (id: string) => ['users', id] as const,
};
export function useUser(userId: string) {
return useQuery({
queryKey: userKeys.detail(userId),
queryFn: () => fetchUserById(userId), // Stable reference to fetchUserById
enabled: !!userId, // Only run if userId exists
});
}
```
### ❌ BAD: Anonymous Query Functions
```typescript
// hooks/useUser.ts
import { useQuery } from '@tanstack/react-query';
export function useUser(userId: string) {
return useQuery({
queryKey: ['users', userId],
// This anonymous function is recreated on every render, potentially causing issues
queryFn: async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
},
enabled: !!userId,
});
}
```
## 4. Conditional Fetching: Use `enabled`
**ALWAYS** use the `enabled` option for conditional fetching. This is the explicit and recommended way to control when a query runs.
### ✅ GOOD: Using `enabled`
```typescript
// hooks/useUserProfile.ts
import { useQuery } from '@tanstack/react-query';
import { fetchUserProfile } from '../api';
export function useUserProfile(userId?: string) {
return useQuery({
queryKey: ['userProfile', userId],
queryFn: () => fetchUserProfile(userId!),
enabled: !!userId, // Query only runs if userId is truthy
});
}
```
### ❌ BAD: Conditional Hook Calls
```typescript
// components/UserProfile.tsx
import { useUserProfile } from '../hooks/useUserProfile';
function UserProfile({ userId }: { userId?: string }) {
// React Hook Rules: Hooks must be called unconditionally
// This breaks the rules and will cause bugs
if (!userId) {
return null;
}
const { data: user, isLoading } = useUserProfile(userId);
// ...
}
```
## 5. Data Transformation: Use `select`
**ALWAYS** use the `select` option within `useQuery` for transforming or filtering data. This ensures the transformation happens once at the query level, optimizing performance and preventing redundant calculations in components.
### ✅ GOOD: `select` for Transformations
```typescript
// hooks/useActiveUsers.ts
import { useQuery } from '@tanstack/react-query';
import { fetchUsers, User } from '../api';
export function useActiveUsers() {
return useQuery<User[], Error, string[]>({ // Specify transformed data type
queryKey: ['users', 'all'],
queryFn: fetchUsers,
select: (data) => data.filter(user => user.status === 'active').map(user => user.name),
});
}
// components/ActiveUserNames.tsx
import { useActiveUsers } from '../hooks/useActiveUsers';
function ActiveUserNames() {
const { data: activeUserNames, isLoading } = useActiveUsers();
if (isLoading) return <div>Loading active users...</div>;
return (
<ul>
{activeUserNames?.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
);
}
```
### ❌ BAD: Transforming Data in Every Component
```typescript
// components/ActiveUserNames.tsx
import { useQuery } from '@tanstack/react-query';
import { fetchUsers } from '../api';
function ActiveUserNames() {
const { data: users, isLoading } = useQuery({
queryKey: ['users', 'all'],
queryFn: fetchUsers,
});
// Transformation logic repeated or inefficiently placed
const activeUserNames = users?.filter(user => user.status === 'active').map(user => user.name);
if (isLoading) return <div>Loading active users...</div>;
return (
<ul>
{activeUserNames?.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
);
}
```
## 6. Mutations and Cache Invalidation
**ALWAYS** use `useMutation` for CUD (Create, Update, Delete) operations. After a successful mutation, **ALWAYS** invalidate relevant queries to ensure the UI reflects the latest server state. For immediate feedback, consider optimistic updates with `setQueryData`.
### ✅ GOOD: Invalidation after Mutation
```typescript
// hooks/useCreateTodo.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createTodo, Todo } from '../api';
export function useCreateTodo() {
const queryClient = useQueryClient();
return useMutation<Todo, Error, { title: string }>({
mutationFn: createTodo,
onSuccess: () => {
// Invalidate all 'todos' queries to refetch fresh data
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}
// components/TodoForm.tsx
import { useCreateTodo } from '../hooks/useCreateTodo';
function TodoForm() {
const { mutate, isLoading } = useCreateTodo();
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
const formData = new FormData(event.currentTarget as HTMLFormElement);
const title = formData.get('title') as string;
mutate({ title });
};
return (
<form onSubmit={handleSubmit}>
<input name="title" placeholder="New todo" />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Adding...' : 'Add Todo'}
</button>
</form>
);
}
```
### ✅ GOOD: Optimistic Updates
```typescript
// hooks/useUpdateTodo.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { updateTodo, Todo } from '../api';
export function useUpdateTodo() {
const queryClient = useQueryClient();
return useMutation<Todo, Error, Partial<Todo> & { id: string }>({
mutationFn: updateTodo,
// Optimistically update the cache
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const previousTodos = queryClient.getQueryData<Todo[]>(['todos']);
queryClient.setQueryData<Todo[]>(['todos'], (old) =>
old ? old.map((todo) => (todo.id === newTodo.id ? { ...todo, ...newTodo } : todo)) : []
);
return { previousTodos }; // Context for onError
},
onError: (err, newTodo, context) => {
// Rollback on error
queryClient.setQueryData(['todos'], context?.previousTodos);
},
onSettled: () => {
// Always refetch after error or success to ensure data is in sync
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}
```
## 7. Performance: Prefetching & Defaults
**LEVERAGE** TanStack Query's defaults (e.g., `staleTime: 0`, automatic retries, refetch on window focus) and **STRATEGICALLY** use prefetching for critical user flows.
### ✅ GOOD: Prefetching for Router Integration
```typescript
// utils/routeLoaders.ts (Example with a router loader)
import { QueryClient } from '@tanstack/react-query';
import { fetchProjectById } from '../api';
export const projectLoader = (queryClient: QueryClient) => async ({ params }: { params: { projectId: string } }) => {
const queryKey = ['projects', params.projectId];
// Prefetch the project data during navigation
await queryClient.prefetchQuery({
queryKey,
queryFn: () => fetchProjectById(params.projectId),
});
return null; // Or return initial data if needed
};
// components/ProjectLink.tsx
import { Link } from 'react-router-dom'; // Assuming react-router-dom
import { useQueryClient } from '@tanstack/react-query';
import { fetchProjectById } from '../api';
function ProjectLink({ projectId, projectName }: { projectId: string; projectName: string }) {
const queryClient = useQueryClient();
const handleMouseEnter = () => {
// Prefetch on hover for instant page loads
queryClient.prefetchQuery({
queryKey: ['projects', projectId],
queryFn: () => fetchProjectById(projectId),
staleTime: 5 * 60 * 1000, // Keep data fresh for 5 minutes
});
};
return (
<Link to={`/projects/${projectId}`} onMouseEnter={handleMouseEnter}>
{projectName}
</Link>
);
}
```
## 8. ESLint Plugin: Enforce Standards
**ALWAYS** install and configure the `@tanstack/query-eslint-plugin`. It enforces many of these best practices automatically, catching common mistakes early.
```json
// .eslintrc.json
{
"plugins": ["@tanstack/query"],
"rules": {
"@tanstack/query/exhaustive-deps": "error",
"@tanstack/query/prefer-query-object": "error",
"@tanstack/query/stable-query-client": "error"
}
}
```
-107
View File
@@ -1,107 +0,0 @@
---
description: "TanStack Query v5 (React Query) patterns including queryOptions helper, query key factories, mutations, optimistic updates, infinite queries, Suspense mode, and prefetching"
globs: ["src/**/*.tsx", "src/**/*.ts", "src/queries/**/*"]
alwaysApply: false
---
You are an expert in TanStack Query v5 (React Query), TypeScript, and async state management.
## Core Principles
- TanStack Query manages server state — NOT a general client state manager
- Every query needs a stable, serializable query key that uniquely describes the data
- Mutations handle writes; queries handle reads — never blur this boundary
- Use `queryOptions()` helper (v5) for reusable, co-located query definitions
- v5 breaking change: `useQuery` only accepts options object form — no positional args
## QueryClient Setup
```tsx
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60,
retry: (count, error: any) => error?.status !== 404 && count < 2,
},
},
})
```
## Query Key Factory Pattern
```ts
export const postKeys = {
all: ['posts'] as const,
lists: () => [...postKeys.all, 'list'] as const,
list: (filters?: PostFilters) => [...postKeys.lists(), filters] as const,
details: () => [...postKeys.all, 'detail'] as const,
detail: (id: string) => [...postKeys.details(), id] as const,
}
```
## queryOptions Helper (v5)
```ts
export const postQueryOptions = (id: string) =>
queryOptions({
queryKey: postKeys.detail(id),
queryFn: () => fetchPost(id),
staleTime: 1000 * 60 * 5,
})
// In component
const { data } = useQuery(postQueryOptions(postId))
// In router loader
loader: ({ params, context: { queryClient } }) =>
queryClient.ensureQueryData(postQueryOptions(params.postId))
```
## Mutations
```tsx
const { mutate, isPending } = useMutation({
mutationFn: (input: CreatePostInput) => createPost(input),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: postKeys.lists() })
},
onError: (error) => toast.error(error.message),
})
```
## Optimistic Updates
```tsx
const mutation = useMutation({
mutationFn: updatePost,
onMutate: async (updated) => {
await queryClient.cancelQueries({ queryKey: postKeys.detail(updated.id) })
const previous = queryClient.getQueryData(postKeys.detail(updated.id))
queryClient.setQueryData(postKeys.detail(updated.id), updated)
return { previous }
},
onError: (_, updated, ctx) => {
queryClient.setQueryData(postKeys.detail(updated.id), ctx?.previous)
},
onSettled: (_, __, updated) => {
queryClient.invalidateQueries({ queryKey: postKeys.detail(updated.id) })
},
})
```
## Infinite Queries
```tsx
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: postKeys.lists(),
queryFn: ({ pageParam }) => fetchPosts({ cursor: pageParam }),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
const allPosts = data?.pages.flatMap((p) => p.items) ?? []
```
## Suspense Mode (v5)
```tsx
// useSuspenseQuery — no isLoading needed, Suspense handles it
const { data } = useSuspenseQuery(postQueryOptions(postId))
// Wrap with <Suspense fallback={<Skeleton />}> + <ErrorBoundary>
```
## Key Rules
- Always define `queryOptions` outside components — never inline in `useQuery()`
- Never use `useEffect` to fetch data — use loaders or `useQuery`
- Use `placeholderData: keepPreviousData` for pagination to avoid layout shifts
- Instantiate `QueryClient` once at app root — never inside a component
-98
View File
@@ -1,98 +0,0 @@
---
description: "Type-safe routing with TanStack Router v1 for React apps, including file-based routing, loaders, search params validation, auth guards, and TanStack Query integration"
globs: ["src/routes/**/*", "src/routeTree.gen.ts", "app.config.ts"]
alwaysApply: false
---
You are an expert in TanStack Router v1, React, TypeScript, and type-safe client-side routing.
## Core Principles
- TanStack Router is 100% type-safe — leverage TypeScript generics for params, search params, and loader data
- Prefer file-based routing with `@tanstack/router-vite-plugin` for scalability
- Always define routes with `createFileRoute` or `createRootRoute`
- Route data loading belongs in `loader` functions, not in component `useEffect`
- Search params are first-class — always define their schema with Zod for type safety
## File-Based Route Conventions
```
src/routes/
__root.tsx ← Root layout
index.tsx ← / route
posts/
index.tsx ← /posts
$postId.tsx ← /posts/:postId (dynamic)
_layout.tsx ← Layout route (no path segment)
_auth/ ← Pathless auth layout group
dashboard.tsx
```
## Route Definition
```tsx
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => fetchPost(params.postId),
component: PostComponent,
errorComponent: ({ error }) => <ErrorBanner message={error.message} />,
pendingComponent: () => <PostSkeleton />,
})
function PostComponent() {
const post = Route.useLoaderData() // type-safe
const { postId } = Route.useParams() // type-safe
return <div>{post.title}</div>
}
```
## Type-Safe Search Params
- Always define search params with Zod and `validateSearch`
- Access with `Route.useSearch()` — never read `window.location.search` directly
```tsx
const searchSchema = z.object({
page: z.number().int().min(1).default(1),
q: z.string().optional(),
})
export const Route = createFileRoute('/search')({
validateSearch: searchSchema,
component: SearchPage,
})
```
## Navigation
- Use `<Link>` for internal navigation — never `<a href>`
- Always pass typed `params` and `search` — the compiler will catch mistakes
```tsx
<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>
```
## Loaders + TanStack Query Integration
```tsx
export const Route = createFileRoute('/posts')({
loader: ({ context: { queryClient } }) =>
queryClient.ensureQueryData(postsQueryOptions()),
component: PostsPage,
})
```
## Router Context for Dependency Injection
```tsx
// __root.tsx
interface RouterContext { queryClient: QueryClient; auth: AuthState }
export const Route = createRootRouteWithContext<RouterContext>()({ component: RootLayout })
// main.tsx
const router = createRouter({ routeTree, context: { queryClient, auth } })
```
## Auth Guards
```tsx
export const Route = createFileRoute('/_auth/dashboard')({
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) throw redirect({ to: '/login' })
},
component: Dashboard,
})
```
## Performance
- Set `defaultPreload: 'intent'` on router for automatic prefetching on hover/focus
- Use `React.lazy` for route component code splitting
- Install `@tanstack/router-devtools` and render `<TanStackRouterDevtools />` in development
-123
View File
@@ -1,123 +0,0 @@
---
description: "TanStack Start full-stack React framework using server functions, API routes, SSR, streaming with defer(), and multi-platform deployment via Vinxi/Nitro"
globs: ["src/routes/**/*", "src/server/**/*", "app.config.ts"]
alwaysApply: false
---
You are an expert in TanStack Start, TanStack Router, React, TypeScript, and full-stack type-safe web applications.
## Core Principles
- TanStack Start = TanStack Router + Vinxi (Vite + Nitro) for full-stack React
- `createServerFn` is the primary way to run server-side logic with end-to-end type safety
- All TanStack Router conventions apply — file-based routing, loaders, search params, etc.
- Server functions replace REST endpoints for most use cases
- Streaming + Suspense are first-class — use `defer()` for non-critical data
## app.config.ts
```ts
import { defineConfig } from '@tanstack/start/config'
import tsConfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
vite: { plugins: [tsConfigPaths()] },
server: {
preset: 'node-server', // or: 'vercel', 'netlify', 'bun', 'cloudflare-pages'
},
})
```
## Root Route HTML Shell
```tsx
// src/routes/__root.tsx
export const Route = createRootRoute({
component: () => (
<html lang="en">
<head />
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
),
})
```
## Server Functions
```ts
// src/server/functions/posts.ts
export const getPost = createServerFn()
.validator(z.object({ id: z.string() }))
.handler(async ({ data }) => {
const post = await db.post.findUnique({ where: { id: data.id } })
if (!post) throw new Error('Post not found')
return post
})
export const createPost = createServerFn()
.validator(z.object({ title: z.string().min(1), body: z.string() }))
.handler(async ({ data }) => db.post.create({ data }))
```
## Using Server Functions in Routes
```tsx
export const Route = createFileRoute('/posts/$postId')({
loader: ({ params }) => getPost({ data: { id: params.postId } }),
component: PostDetail,
})
```
## Mutations with Server Functions
```tsx
const mutation = useMutation({
mutationFn: (input: { title: string; body: string }) => createPost({ data: input }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
})
```
## API Routes (for webhooks / raw HTTP)
```ts
// src/routes/api/webhook.ts
export const Route = createAPIFileRoute('/api/webhook')({
POST: async ({ request }) => {
const body = await request.json()
return Response.json({ received: true })
},
})
```
## Streaming with defer()
```tsx
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await getPost({ data: { id: params.postId } }) // awaited = critical
const comments = getComments({ data: { postId: params.postId } }) // not awaited
return { post, comments: defer(comments) }
},
component: PostDetail,
})
function PostDetail() {
const { post, comments } = Route.useLoaderData()
return (
<div>
<h1>{post.title}</h1>
<Suspense fallback={<CommentsSkeleton />}>
<Await promise={comments}>{(c) => <CommentsList comments={c} />}</Await>
</Suspense>
</div>
)
}
```
## Environment Variables
- Access server-only vars via `process.env` inside server functions only
- Use `import.meta.env.VITE_*` for client-exposed variables
- Never access `process.env` in client components
## Deployment Targets
Configure `server.preset` in `app.config.ts`:
- `node-server` — default Node.js
- `vercel` — Vercel serverless/edge
- `netlify` — Netlify Functions
- `bun` — Bun runtime
- `cloudflare-pages` — Cloudflare Pages + Workers
@@ -1,68 +0,0 @@
---
description: "Cursor rules for TypeScript development with Vite and Tailwind integration."
globs: **/*
alwaysApply: false
---
You are an expert in TypeScript, Node.js, Vite, Vue.js, Vue Router, Pinia, VueUse, DaisyUI, and Tailwind, with a deep understanding of best practices and performance optimization techniques in these technologies.
Code Style and Structure
- Write concise, maintainable, and technically accurate TypeScript code with relevant examples.
- Use functional and declarative programming patterns; avoid classes.
- Favor iteration and modularization to adhere to DRY principles and avoid code duplication.
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError).
- Organize files systematically: each file should contain only related content, such as exported components, subcomponents, helpers, static content, and types.
Naming Conventions
- Use lowercase with dashes for directories (e.g., components/auth-wizard).
- Favor named exports for functions.
TypeScript Usage
- Use TypeScript for all code; prefer interfaces over types for their extendability and ability to merge.
- Avoid enums; use maps instead for better type safety and flexibility.
- Use functional components with TypeScript interfaces.
Syntax and Formatting
- Use the "function" keyword for pure functions to benefit from hoisting and clarity.
- Always use the Vue Composition API script setup style.
UI and Styling
- Use DaisyUI, and Tailwind for components and styling.
- Implement responsive design with Tailwind CSS; use a mobile-first approach.
Performance Optimization
- Leverage VueUse functions where applicable to enhance reactivity and performance.
- Wrap asynchronous components in Suspense with a fallback UI.
- Use dynamic loading for non-critical components.
- Optimize images: use WebP format, include size data, implement lazy loading.
- Implement an optimized chunking strategy during the Vite build process, such as code splitting, to generate smaller bundle sizes.
Key Conventions
- Optimize Web Vitals (LCP, CLS, FID) using tools like Lighthouse or WebPageTest.
- Use the VueUse library for performance-enhancing functions.
- Implement lazy loading for non-critical components.
- Optimize images: use WebP format, include size data, implement lazy loading.
- Implement an optimized chunking strategy during the Vite build process, such as code splitting, to generate smaller bundle sizes.
Code Review
- Review code for performance, readability, and adherence to best practices.
- Ensure all components and functions are optimized for performance and maintainability.
- Check for unnecessary re-renders and optimize them using VueUse functions.
- Use the VueUse library for performance-enhancing functions.
- Implement lazy loading for non-critical components.
- Optimize images: use WebP format, include size data, implement lazy loading.
- Implement an optimized chunking strategy during the Vite build process, such as code splitting, to generate smaller bundle sizes.
Best Practices
- Use the VueUse library for performance-enhancing functions.
- Implement lazy loading for non-critical components.
- Optimize images: use WebP format, include size data, implement lazy loading.
- Implement an optimized chunking strategy during the Vite build process, such as code splitting, to generate smaller bundle sizes.
@@ -1,137 +0,0 @@
---
alwaysApply: true
---
# The Ultimate Frontend Development Guide: Principles, Patterns, and Practices
## Development Philosophy
- **First Principles**: Embrace SOLID principles, KISS (Keep It Simple, Stupid), and DRY (Don't Repeat Yourself)
- **Functional Over Object-Oriented**: Favor functional and declarative programming patterns over imperative and OOP
- **Component-Driven Development**: Build applications as compositions of well-defined, reusable components
- **Type Safety**: Leverage TypeScript to its fullest potential for enhanced developer experience and code quality
- **Think Then Code**: Begin with step-by-step planning and detailed pseudocode before implementation
## Code Architecture & Structure
### Project Organization
- Use lowercase with dashes for directories (`components/auth-wizard/`)
- Structure files consistently:
1. Exported component/functionality
2. Subcomponents/helpers
3. Static content
4. Types/interfaces
### Naming Conventions
- **PascalCase** for:
- Components (`UserProfile`)
- Type definitions/Interfaces (`UserData`)
- **kebab-case** for:
- Directory names (`components/auth-wizard/`)
- File names (`user-profile.tsx`)
- **camelCase** for:
- Variables, functions, methods
- Hooks, properties, props
- **Descriptive Prefixes**:
- Prefix event handlers with 'handle': `handleClick`, `handleSubmit`
- Prefix boolean variables with verbs: `isLoading`, `hasError`, `canSubmit`
- Prefix custom hooks with 'use': `useAuth`, `useForm`
## TypeScript Implementation
- Enable strict mode
- Prefer interfaces over types for object structures, especially when extending
- Use type guards for null/undefined values
- Apply generics for type flexibility
- Leverage TypeScript utility types (`Partial<>`, `Pick<>`, `Omit<>`)
- Avoid enums; use const objects/maps instead
- Use discriminated unions for complex state management
## React & Next.js Best Practices
### Component Patterns
- Use functional components with explicit TypeScript interfaces
- Use the `function` keyword for component definitions, not arrow functions
- Extract reusable logic into custom hooks
- Place static content in variables outside render functions
- Implement proper cleanup in useEffect hooks
### Server Components First
- Default to Server Components
- Use `'use client'` directive sparingly, only when necessary:
- Event listeners
- Browser APIs
- State that must be client-side
- Client-side-only libraries
- Use URL query parameters for server state management
- Implement proper data fetching using Next.js patterns
### Performance Optimizations
- Use React.memo() strategically
- Implement useCallback for event handlers passed to child components
- Use useMemo for expensive computations
- Avoid inline function definitions in JSX
- Implement code splitting using dynamic imports
- Use proper key props in lists (avoid using index as key)
- Wrap client components in Suspense with appropriate fallbacks
## UI and Styling
- Use Tailwind CSS for utility-first, maintainable styling
- Leverage component libraries like Shadcn UI and Radix UI for accessible, composable UI
- Design with mobile-first, responsive principles
- Implement dark mode using CSS variables or Tailwind's dark mode features
- Maintain consistent spacing values and design tokens
- Use Framer Motion library for the animations of components
## Error Handling - The Art of Graceful Failures
### The Early Return Pattern
- Handle errors and edge cases at the beginning of functions
- Use early returns for error conditions
- Place the happy path last in the function
- Avoid unnecessary else statements; use if-return pattern instead
- Implement guard clauses to handle preconditions
### Structured Error Handling
- Use custom error types for consistent error handling
- For Next.js Server Actions, model expected errors as return values
- Implement error boundaries using error.tsx files
- Provide user-friendly error messages
- Log errors appropriately for debugging
## Form Validation
- Use Zod for schema validation
- Implement proper error messages
- Use react-hook-form for form state management
- Combine with useActionState for server actions
## State Management
- Use useState for simple component-level state
- Implement useReducer for complex local state
- Use React Context for shared state within a component tree
- For global state, choose appropriate tools:
- Redux Toolkit for complex applications
- Zustand for simpler state management
- TanStack Query for server state
## Accessibility (a11y)
- Use semantic HTML elements
- Apply appropriate ARIA attributes only when necessary
- Ensure keyboard navigation support
- Maintain accessible color contrast ratios
- Follow a logical heading hierarchy
- Provide clear and accessible error feedback
- Test with screen readers
+120
View File
@@ -0,0 +1,120 @@
---
description: Vite + TanStack Router v1 + TanStack Query v5 — routing, loaders, queries, mutations
globs: apps/web/**/*.{tsx,ts}
alwaysApply: false
---
# Vite + TanStack Router + Query
Фронтенд: **Vite SPA**, не Next.js. Нет Server Components, App Router, `'use client'`.
## Структура
```
apps/web/src/
routes/ # file-based routes (__root.tsx, _auth/, ...)
queries/ # queryOptions factories + key factories
lib/ # api-client, queryClient, auth, schemas
components/ # domain + layout (UI primitives → @cfdm/ui)
main.tsx
```
## Архитектура
- **Router** — маршрутизация, URL state, navigation, loaders
- **Query** — server state, cache, mutations
- **Loader** — `queryClient.ensureQueryData()` до рендера → без спиннеров на route data
- **Компоненты** — UI; данные из Query cache
## QueryClient + Router
```ts
// lib/queryClient.ts
export const queryClient = new QueryClient({
defaultOptions: { queries: { staleTime: 60_000 } },
})
// lib/router.ts
export const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: 'intent',
})
declare module '@tanstack/react-router' {
interface Register { router: typeof router }
}
```
## Query definitions
- `queryOptions` factories в `queries/`, не inline в компонентах
- Key factories: `all` → `lists` / `details` → `list(filters)` / `detail(id)`
```ts
export const serviceKeys = {
all: ['services'] as const,
list: () => [...serviceKeys.all, 'list'] as const,
}
export const servicesQueryOptions = () =>
queryOptions({
queryKey: serviceKeys.list(),
queryFn: () => api.get('/api/v1/services'),
})
```
## Loader + component
```tsx
export const Route = createFileRoute('/_auth/services')({
loader: ({ context: { queryClient } }) =>
queryClient.ensureQueryData(servicesQueryOptions()),
component: ServicesPage,
})
function ServicesPage() {
const { data } = useQuery(servicesQueryOptions()) // из cache loader
return ...
}
```
## Search params
- Zod + `validateSearch`; доступ через `Route.useSearch()`
- Search params = source of truth для фильтров/пагинации
- Передавать в `queryOptions` для query key и fetcher
## Mutations
```ts
onSuccess: (newItem) => {
queryClient.setQueryData(keys.detail(newItem.id), newItem)
queryClient.invalidateQueries({ queryKey: keys.lists() })
}
```
- `setQueryData` + `invalidateQueries`, не только invalidate
- Навигация после create — когда cache уже тёплый
## Routing
- `createFileRoute` для file-based routes
- `<Link>` для внутренней навигации, не `<a href>`
- Pathless layouts: `_auth/` для protected routes
- Auth guard в `beforeLoad` pathless route
## Запреты
- `useEffect` для fetch данных — только loader / `useQuery`
- Inline `queryKey` в компонентах — только factories из `queries/`
- `useQuery` с позиционными аргументами (v5 — только options object)
- `window.location` для search params
## Prefetch
`onMouseEnter` на `<Link>` → `queryClient.prefetchQuery(detailOptions(id))`
## DevTools
Только в dev: `TanStackRouterDevtools`, `ReactQueryDevtools`
-300
View File
@@ -1,300 +0,0 @@
---
description: This guide provides definitive best practices for developing high-performance, maintainable applications with Vite, focusing on optimal configuration, code structure, and testing.
globs: **/*.{js,jsx}
---
# vite Best Practices
Vite is the modern standard for frontend tooling. Adhere to these principles to leverage its full potential, ensuring blazing-fast development and optimized production builds.
## 1. Code Organization and Structure
### Keep `vite.config.js` Minimal
Vite's philosophy is a lean core. Avoid over-configuring. Only add plugins or options when absolutely necessary.
❌ **BAD** - Overly complex `vite.config.js`
```javascript
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import legacy from '@vitejs/plugin-legacy';
import { visualizer } from 'rollup-plugin-visualizer';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
react(),
legacy({ targets: ['defaults', 'not IE 11'] }),
visualizer({ filename: './dist/stats.html' }),
VitePWA({ registerType: 'autoUpdate' }),
// ... many more plugins
],
resolve: {
alias: {
'@': '/src',
'~': '/node_modules',
},
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
},
build: {
target: 'es2015',
minify: 'terser',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
},
server: {
port: 3000,
open: true,
proxy: {
'/api': 'http://localhost:8080',
},
},
});
```
✅ **GOOD** - Lean and focused `vite.config.js`
```javascript
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
// Only add resolve.alias if absolutely needed for complex paths.
// Avoid resolve.extensions unless you have specific non-standard file types.
// Vite's defaults are usually sufficient.
});
```
### Use Explicit File Extensions
Relying on `resolve.extensions` for implicit imports forces Vite to perform multiple filesystem checks, slowing down resolution. Be explicit.
❌ **BAD** - Implicit import
```javascript
// src/components/MyComponent.jsx
import { util } from '../utils'; // Vite has to guess .js, .ts, .jsx etc.
```
✅ **GOOD** - Explicit import
```javascript
// src/components/MyComponent.jsx
import { util } from '../utils/index.js'; // Or .ts, .jsx, etc.
```
### Avoid Barrel Files
Barrel files (e.g., `index.js` re-exporting many modules) force Vite to fetch and transform all re-exported files, even if only one API is used. This hurts initial page load performance.
❌ **BAD** - Barrel file (`src/utils/index.js`)
```javascript
// src/utils/index.js
export * from './color.js';
export * from './dom.js';
export * from './slash.js';
// src/app.js
import { slash } from './utils'; // Loads color.js, dom.js, and slash.js
```
✅ **GOOD** - Direct imports
```javascript
// src/app.js
import { slash } from './utils/slash.js'; // Only loads slash.js
```
## 2. Common Patterns and Anti-patterns
### Embrace Native ES Modules
Vite is built on native ES Modules. Always write your client-side code using `import`/`export` syntax.
❌ **BAD** - CommonJS in client-side code
```javascript
// main.js
const myModule = require('./my-module'); // Will fail in browser
```
✅ **GOOD** - Native ES Modules
```javascript
// main.js
import myModule from './my-module.js';
```
### Use `import.meta.env` for Environment Variables
Vite injects environment variables via `import.meta.env`. This is the correct way to access them in client-side code. `process.env` is for Node.js environments.
❌ **BAD** - Using `process.env` in client code
```javascript
// app.js
console.log(process.env.VITE_API_URL); // `process` is not defined in browser
```
✅ **GOOD** - Using `import.meta.env`
```javascript
// app.js
console.log(import.meta.env.VITE_API_URL); // Correctly accesses Vite env vars
```
### Optimize with Dynamic Imports
For large components or libraries, use dynamic imports to load them only when needed, reducing initial bundle size and improving load times.
❌ **BAD** - Eagerly loading large component
```javascript
// App.jsx
import LargeComponent from './LargeComponent'; // Always bundled
function App() {
return <LargeComponent />;
}
```
✅ **GOOD** - Dynamically importing
```javascript
// App.jsx (React example)
import { lazy, Suspense } from 'react';
const LargeComponent = lazy(() => import('./LargeComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LargeComponent />
</Suspense>
);
}
```
## 3. Performance Considerations
### Audit Custom Plugins
Community plugins can introduce performance bottlenecks. Profile them using Vite's debug flags.
❌ **BAD** - Blindly adding plugins
```javascript
// vite.config.js
import { defineConfig } from 'vite';
import someHeavyPlugin from 'some-heavy-plugin'; // No profiling done
export default defineConfig({
plugins: [someHeavyPlugin()],
});
```
✅ **GOOD** - Profiling plugins
```bash
# Run Vite with debug flags to identify slow plugins
vite --debug plugin-transform
```
Use `vite-plugin-inspect` to visualize the transform pipeline.
### Optimize Browser Setup
Browser extensions and disabled cache settings can severely impact dev server performance.
❌ **BAD** - Developing with "Disable Cache" enabled in dev tools.
```
// Browser Dev Tools -> Network tab -> "Disable Cache" checked
```
✅ **GOOD** - Use a clean browser profile or incognito mode.
Ensure "Disable Cache" is **unchecked** in dev tools.
### Warm Up Critical Files
For complex applications, pre-warming frequently used files can prevent request waterfalls.
❌ **BAD** - Relying solely on on-demand transformation for critical paths.
```javascript
// No explicit warmup configured
```
✅ **GOOD** - Use `server.warmup` in `vite.config.js`
```javascript
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
server: {
warmup: {
clientFiles: ['./src/main.js', './src/App.jsx'],
// Or use patterns: ['**/*.vue', '**/*.jsx']
},
},
});
```
## 4. Common Pitfalls and Gotchas
### Incorrect Base Path for Deployment
When deploying to a sub-path (e.g., `yourdomain.com/my-app/`), ensure `base` is correctly configured.
❌ **BAD** - Hardcoding absolute paths or missing `base`
```javascript
// vite.config.js
// Default base: '/'
// Assets might break when deployed to a sub-path
```
✅ **GOOD** - Configure `base` for sub-path deployments
```javascript
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
base: '/my-app/', // For deploying to https://yourdomain.com/my-app/
// Or use './' for relative paths if the base is unknown at build time
// base: './',
});
```
Access the base path in your code via `import.meta.env.BASE_URL`.
### Mismanaging `NODE_ENV` with API Usage
When using Vite's JS API (`createServer`, `build`) in the same Node.js process, ensure `process.env.NODE_ENV` or the `mode` config option is consistent to prevent conflicts.
❌ **BAD** - Conflicting `NODE_ENV`
```javascript
// script.js
process.env.NODE_ENV = 'production';
await createServer(); // Might behave unexpectedly
```
✅ **GOOD** - Explicitly set `mode` or spawn child processes
```javascript
// script.js
import { createServer } from 'vite';
// Option 1: Explicitly set mode
const devServer = await createServer({ mode: 'development' });
await devServer.listen();
// Option 2: Spawn child processes for separate contexts
// (e.g., one for dev server, one for build)
```
## 5. Testing Approaches
### Standardize on Vitest
Vitest is the official testing framework for Vite projects, offering seamless integration with Vite's configuration and plugin ecosystem.
❌ **BAD** - Using a separate test runner (e.g., Jest) that requires its own complex configuration.
```json
// package.json
"scripts": {
"test": "jest" // Requires separate Babel/Webpack config
}
```
✅ **GOOD** - Integrate Vitest directly into `vite.config.ts`
```typescript
// vite.config.ts
/// <reference types="vitest/config" />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true, // For global APIs like `describe`, `it`, `expect`
environment: 'jsdom', // Or 'node'
setupFiles: './src/setupTests.js', // Global setup for tests
},
});
```