- name
- code
- description
- Use when writing TypeScript/React code - covers type safety, component patterns, and file organization
Source Cursor rule: `.cursor/rules/code.mdc`.
Original Cursor alwaysApply: `false`.
# Code Standards
## TypeScript
### No `any`, No Unsafe Casts
```tsx
// ✅ Validate with zod
const TaskSchema = z.object({ id: z.string(), title: z.string() });
const task = TaskSchema.parse(response.data);
// ✅ Use unknown and narrow
const parseResponse = (data: unknown): Task => {
if (!isTask(data)) throw new Error('Invalid');
return data;
};
// ❌ Never
const data: any = fetchData();
const task = response as Task;
const name = user!.name;
// @ts-ignore
```
### Generics Over Any
```tsx
// ✅ Generic
const first = <T>(items: T[]): T | undefined => items[0];
// ❌ Any
const first = (items: any[]): any => items[0];
```
## React Patterns
### Named Exports, PascalCase
```tsx
// ✅ Named export, PascalCase file
// TaskCard.tsx
export function TaskCard({ task }: TaskCardProps) { ... }
// ❌ Default export, lowercase
export default function taskCard() { ... }
```
### Derive State, Avoid useEffect
```tsx
// ✅ Derived
const completedCount = tasks.filter(t => t.completed).length;
// ❌ Synced state
const [count, setCount] = useState(0);
useEffect(() => {
setCount(tasks.filter(t => t.completed).length);
}, [tasks]);
```
### When useEffect IS Appropriate
```tsx
// External subscriptions
useEffect(() => {
const sub = eventSource.subscribe(handler);
return () => sub.unsubscribe();
}, []);
// DOM measurements
useEffect(() => {
setHeight(ref.current?.getBoundingClientRect().height);
}, []);
```
### Toasts with Sonner
```tsx
import { toast } from 'sonner';
toast.success('Task created');
toast.error('Failed to save');
toast.promise(saveTask(), {
loading: 'Saving...',
success: 'Saved!',
error: 'Failed',
});
```
## File Structure
### Colocate at Route Level
```
app/(app)/[orgId]/tasks/
├── page.tsx # Server component
├── components/
│ └── TaskList.tsx # Client component
├── hooks/
│ └── useTasks.ts # SWR hook
└── data/
└── queries.ts # Server queries
```
### Share Only When Reused 3+ Times
```
src/components/shared/ # Cross-page components
src/hooks/ # Shared hooks (useApiSWR, useDebounce)
```
## Code Quality
### File Size Limit: 300 Lines
Split large files into focused components.
### Named Parameters for 2+ Args
```tsx
// ✅ Named
const createTask = ({ title, assigneeId }: CreateTaskParams) => { ... };
createTask({ title: 'Review PR', assigneeId: user.id });
// ❌ Positional
const createTask = (title: string, assigneeId: string) => { ... };
createTask('Review PR', user.id); // What's the 2nd param?
```
### Early Returns
```tsx
// ✅ Early return
function processTask(task: Task | null) {
if (!task) return null;
if (task.deleted) return null;
return <TaskCard task={task} />;
}
// ❌ Nested
function processTask(task) {
if (task) {
if (!task.deleted) {
return <TaskCard task={task} />;
}
}
return null;
}
```
### Event Handler Naming
```tsx
// ✅ Prefix with "handle"
const handleClick = () => { ... };
const handleSubmit = (e: FormEvent) => { ... };
const handleTaskCreate = (task: Task) => { ... };
```
## Accessibility
```tsx
// Interactive elements need keyboard support
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
aria-label="Delete task"
>
<TrashIcon />
</div>
// Form inputs need labels
<label htmlFor="task-name">Task Name</label>
<input id="task-name" type="text" />
```
GitHub에서 보기