Opinionated rules for building scalable, high-performance, React/TypeScript component libraries and web applications.
Building React apps that don't turn into unmaintainable messes? You're about to transform how you architect frontend components.
You've been there: starting with clean, simple components that gradually become 500-line monsters. Props drilling through five levels. State scattered everywhere. Components tightly coupled to specific data sources. Tests that break when you breathe on the code.
The real pain points aren't abstract—they're specific:
any types everywhere because proper typing feels too complexThese Cursor Rules establish a bulletproof component architecture that separates concerns cleanly, enforces TypeScript discipline, and scales from small features to enterprise applications. Think of it as your architectural guardrails that prevent the common pitfalls while accelerating development velocity.
Here's what makes this different from typical component guidelines:
Strict Separation of Concerns: Components handle UI rendering only. Business logic lives in hooks, state management in Redux slices, side effects in service layers. No more 300-line components doing everything.
TypeScript-First Architecture: Strict typing with interfaces for object shapes, proper prop definitions, and zero any types. Your IDE becomes a powerful development assistant instead of a syntax checker.
Performance by Design: Built-in memoization strategies, code splitting patterns, and optimization techniques that prevent performance issues before they start.
// 400+ line component doing everything
const UserDashboard = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Data fetching mixed with UI logic
useEffect(() => {
fetchUsers().then(setUsers).catch(setError);
}, []);
// Business logic scattered throughout
const handleUserUpdate = (userId, data) => {
// 50 lines of update logic...
};
// Massive JSX with inline functions
return (
<div>
{/* 200+ lines of JSX */}
</div>
);
};
// Pure UI component (30 lines)
export interface UserDashboardProps {
users: User[];
onUserUpdate: (userId: string, data: UpdateUserData) => void;
loading: boolean;
}
export const UserDashboard: React.FC<UserDashboardProps> = memo(({
users,
onUserUpdate,
loading
}) => {
if (loading) return <LoadingSpinner />;
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{users.map(user => (
<UserCard
key={user.id}
user={user}
onUpdate={onUserUpdate}
/>
))}
</div>
);
});
// Business logic in custom hook (40 lines)
export const useUserDashboard = () => {
const { data: users, isLoading } = useQuery(['users'], fetchUsers);
const updateUserMutation = useMutation(updateUser);
const handleUserUpdate = useCallback((userId: string, data: UpdateUserData) => {
updateUserMutation.mutate({ userId, data });
}, [updateUserMutation]);
return { users, loading: isLoading, onUserUpdate: handleUserUpdate };
};
// Container component connecting everything (10 lines)
export const UserDashboardContainer: React.FC = () => {
const dashboardProps = useUserDashboard();
return <UserDashboard {...dashboardProps} />;
};
Testing becomes trivial:
// Test the pure UI component
test('renders user cards correctly', () => {
render(<UserDashboard users={mockUsers} onUserUpdate={mockFn} loading={false} />);
expect(screen.getAllByTestId('user-card')).toHaveLength(3);
});
// Test business logic separately
test('handles user updates correctly', () => {
const { result } = renderHook(() => useUserDashboard());
// Test hook logic in isolation
});
Component reuse across features:
// Same UserCard component works everywhere
<UserCard user={user} onUpdate={handleAdminUpdate} /> // Admin panel
<UserCard user={user} onUpdate={handleProfileUpdate} /> // Profile page
<UserCard user={user} onUpdate={handleTeamUpdate} /> // Team management
.cursor/rules/ directory as component-architecture.mdtsconfig.json for strict TypeScript:{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"lib": ["es2022", "dom"]
}
}
npm install -D @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks
src/
app/ # App entry, providers, routing
components/ # Shared UI components
⌞ Button/
⌞ Button.tsx
⌞ Button.test.tsx
⌞ Button.stories.tsx
features/ # Feature-based organization
⌞ users/
⌞ UserDashboard.tsx
⌞ useUserDashboard.ts
⌞ userSlice.ts
hooks/ # Reusable custom hooks
utils/ # Pure utility functions
types/ # Global TypeScript types
Start with your most problematic components and apply this transformation:
For local component state:
const [loading, setLoading] = useState(false);
For cross-component state, use Redux Toolkit:
// userSlice.ts
export const userSlice = createSlice({
name: 'users',
initialState: { users: [], loading: false },
reducers: {
setLoading: (state, action) => {
state.loading = action.payload;
}
}
});
Configure Jest and React Testing Library:
npm install -D jest @testing-library/react @testing-library/jest-dom
Write tests that follow the Arrange-Act-Assert pattern:
test('Button handles click events', () => {
// Arrange
const handleClick = jest.fn();
// Act
render(<Button label="Click me" onClick={handleClick} />);
fireEvent.click(screen.getByText('Click me'));
// Assert
expect(handleClick).toHaveBeenCalledTimes(1);
});
This isn't just about cleaner code—it's about transforming your development workflow into a highly productive, maintainable system that scales with your team and product requirements. Your components become predictable, testable, and reusable building blocks instead of tangled implementations.
The best part? You can implement these patterns incrementally. Start with your most problematic components, apply the ruleset patterns, and watch your development velocity accelerate while code quality improves dramatically.
You are an expert in TypeScript, React 18, Vite, Jest, React Testing Library, Storybook, Redux Toolkit, React Query, Tailwind CSS, ESLint, Prettier.
Key Principles
- Favour composition over inheritance; every component obeys the Single-Responsibility Principle.
- Keep components pure; side-effects live in hooks, state managers, or service layers.
- Prefer declarative code and immutable data structures; never mutate props or global state directly.
- Top-down, unidirectional data flow only. Lift state or use centralised stores instead of prop-drilling.
- Small, reusable, testable units. One component ≤ 200 LOC, one hook ≤ 100 LOC.
- Strict TypeScript everywhere (`strict: true`, `noImplicitAny: true`). No `any`, no `null`—use `undefined`.
- Style guide is enforced by Prettier + ESLint (`@typescript-eslint`, `eslint-plugin-react`, `eslint-plugin-react-hooks`). Build must fail on linting or type errors.
- Directory names: kebab-case; file names: `PascalCase.tsx` for components, `useSomething.ts` for hooks.
TypeScript
- Use `interface` for object shapes, `type` for primitives, unions & generics.
- Never use default exports; favour named exports to enable tree-shaking & refactors.
- Optional props use `?:`, never rely on `| undefined` manually.
- Do not suppress with `// @ts-ignore` unless documented with a Jira link and TODO.
- Enable `es2022` lib for top-level `await`.
JavaScript Runtime Conventions
- Arrow functions for all functional components/hooks. Use named functions for event handlers passed as props.
- Semicolons required (prevents ASI bugs).
- Destructure props & state at top of component body; avoid dot-chaining inside JSX.
Error Handling & Validation
- Validate critical props with TypeScript + `zod` schemas at component boundary if data comes from external source.
- Use React Error Boundaries at layout root and feature slice root; surface user-friendly fallback UIs.
- Inside async hooks/services:
- Handle errors first (`try/catch` or `.catch`) and return early.
- Wrap unknown errors with custom `AppError` maintaining `cause`.
- Never swallow errors; re-throw or log to `Sentry`.
- Always reject Promises with `Error`, never strings.
React Framework Rules
- Functional components exclusively; no class components.
- Hooks order: `useState`/`useReducer`, `useRef`, `useMemo`, `useCallback`, `useEffect`.
- Every file exporting a component must additionally export its `propTypes` TypeScript interface `ComponentProps`.
- UI state local → `useState`; cross-component state → Redux Toolkit slices or React Context if < 3 consumers.
- Data-fetching uses React Query; caching time configured per endpoint.
- Responsive design with Tailwind CSS utility classes + CSS Modules for exceptional styles.
- Use `Suspense` + `lazy()` for code-splitting route-level & heavy components.
- Memoise expensive components: `export const Chart = memo(function Chart(){…})` with meaningful displayName.
- Strict mode enabled everywhere.
Directory & File Structure
```
src/
app/ # entry, providers, routing
components/ # generic shared UI atoms & molecules
features/ # domain-oriented slices (UI + state)
⌞ todo/
⌞ TodoList.tsx
⌞ todoSlice.ts
hooks/ # reusable hooks (no JSX)
utils/ # pure helpers, no IO side-effects
types/ # global re-usable TypeScript types
styles/ # Tailwind config & global styles
tests/ # utility test helpers/mocks
```
State Management Rules
- Redux slices co-located in `features/<domain>/`.
- Use `createSlice`, `createAsyncThunk`; reducers must be pure & mutation-safe (immer in toolkit).
- Never store non-serialisable data in Redux (e.g., Dates as ISO strings).
- Normalise collections with entity adapters.
Performance Optimisation
- Prefer `React.memo`, `useMemo`, `useCallback` only after measurement (React Profiler).
- Break large lists with `react-window`/`react-virtualised`.
- Images: automatic optimisation via `next/image` or `vite-imagemin`.
- Enable HTTP/2 and GZIP/Brotli on hosting.
Testing
- Unit tests with Jest + `@testing-library/react`.
- Arrange/Act/Assert pattern. No shallow rendering.
- Minimum 80 % branch coverage; critical logic ≥ 95 %.
- E2E tests with Playwright for critical paths.
- Storybook stories are required for every public component; use stories as visual regression tests with Chromatic.
Documentation
- JSDoc or TSDoc for all exported functions & types.
- Storybook MDX docs show props table (`argTypes`) and live examples.
- README per feature with context, APIs, screenshots.
CI/CD & Tooling
- GitHub Actions steps: install → lint → type-check → test → build → deploy (Vercel/Netlify).
- Conventional Commits enforced with commitlint; semantic-release for versioning.
- Renovate bot for dependency updates; lockfile scanned by `npm audit` & `snyk`.
Security
- Never interpolate user input into HTML; leverage React escaping by default.
- Enable Content Security Policy headers.
- Environment variables accessed via `import.meta.env` and prefixed with `VITE_`.
- Secrets never committed; checked by `git-secrets`.
Accessibility (a11y)
- All interactive elements must be keyboard reachable and have ARIA labels.
- Colour contrast ratio ≥ 4.5:1 validated by Storybook a11y addon.
- Components pass axe-core tests in CI.
Common Pitfalls & Guardrails
- No prop-drilling > 3 levels; escalate to state manager.
- Avoid anonymous inline functions in JSX for frequently re-rendered lists.
- Don’t use `index` as React key except for static, never-changing arrays.
- Keep hook dependency arrays exhaustive; use ESLint `react-hooks/exhaustive-deps`.
Ready-Made Snippets
```
// Pure component template
export interface ButtonProps {
label: string
onClick: () => void
disabled?: boolean
}
export const Button: React.FC<ButtonProps> = memo(({ label, onClick, disabled = false }) => {
return (
<button
type="button"
onClick={onClick}
disabled={disabled}
className="px-4 py-2 rounded bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-40"
>
{label}
</button>
)
})
Button.displayName = "Button"
```
Follow this rule set strictly to maintain a scalable, maintainable, and high-quality component-based codebase.