Frontend Code Review
This skill provides comprehensive, production-ready code review for modern frontend applications with actionable feedback focused on React/TypeScript/Tailwind stack.
Purpose
Transform frontend code review from manual inspection into systematic analysis covering:
- Frontend Security - XSS, CSRF, sensitive data exposure, auth issues
- React Performance - Re-renders, memoization, bundle size, lazy loading
- Code Quality - Readability, maintainability, React best practices
- Component Architecture - Layered architecture, separation of concerns, reusability
- Type Safety - TypeScript usage, type correctness, runtime validation
- Accessibility - WCAG compliance, keyboard navigation, screen readers
- Responsive Design - Mobile-first, breakpoints, Tailwind patterns
- SEO & Meta - Meta tags, semantic HTML, performance metrics
- Testing - Component tests, hooks tests, edge cases
- State Management - Zustand/Context patterns, React Query usage
When to Use This Skill
Use this skill when:
- User asks for code review or feedback
- User mentions: "review", "check", "feedback", "quality", "security"
- After generating components or features
- User asks about performance or accessibility
- Before committing major changes
- Examples:
- "Review this component"
- "Is this React code optimized?"
- "Can you check for accessibility issues?"
- "How can I improve this?"
- "Review my feature implementation"
Review Process
Step 1: Understand Context
Before reviewing, gather context:
-
Code Type:
- React Component (UI, Form, List, etc.)
- Custom Hook (business logic)
- Utility function (helpers, transforms)
- API integration (React Query, fetch)
- Store/State management (Zustand, Context)
- Styling (Tailwind, CSS-in-JS)
-
Review Scope:
- Single component/hook
- Entire feature (multiple files)
- Page/route implementation
- Shared utilities
-
Priority:
- Security-critical (auth, payment forms)
- Performance-critical (large lists, complex calculations)
- User-facing (accessibility, UX)
- Internal (utilities, helpers)
Step 2: Initial Scan
Quickly scan for obvious issues:
Critical Issues (π¨ CRITICAL):
- XSS vulnerabilities (dangerouslySetInnerHTML)
- CSRF vulnerabilities (missing tokens)
- Sensitive data exposure (tokens in localStorage)
- Authentication bypass
- Hardcoded secrets/API keys
High Priority (β οΈ HIGH):
- Performance bottlenecks (unnecessary re-renders, no memoization)
- Memory leaks (missing cleanup in useEffect)
- Error handling gaps
- Accessibility violations (no ARIA labels, keyboard support)
- Missing input validation
<br>Medium Priority (β‘ MEDIUM):
- Code duplication
- Unclear component/variable names
- Missing loading/error states
- Poor TypeScript usage (any types)
- Inconsistent Tailwind usage
Low Priority (π‘ LOW):
- Code style inconsistencies
- Missing comments for complex logic
- Minor optimizations
- Documentation gaps
Step 3: Deep Analysis
Perform systematic review across all dimensions:
3.1 Frontend Security Review
Check against common frontend vulnerabilities:
// β BAD: XSS vulnerability
function UserComment({ comment }: { comment: string }) {
return <div dangerouslySetInnerHTML={{ __html: comment }} />;
}
// β
GOOD: Sanitized HTML
import DOMPurify from 'dompurify';
function UserComment({ comment }: { comment: string }) {
return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(comment) }} />;
}
// β
BETTER: No HTML, just text
function UserComment({ comment }: { comment: string }) {
return <div>{comment}</div>;
}
// β BAD: Token in localStorage (XSS vulnerable)
localStorage.setItem('token', response.token);
// β
GOOD: HttpOnly cookie (set by server)
// Or use secure session management library
// β BAD: Hardcoded API key
const API_KEY = "pk_live_abc123xyz";
// β
GOOD: Environment variable
const API_KEY = process.env.NEXT_PUBLIC_API_KEY;
// β BAD: No CSRF protection
async function transferMoney(to: string, amount: number) {
await fetch('/api/transfer', {
method: 'POST',
body: JSON.stringify({ to, amount })
});
}
// β
GOOD: Include CSRF token
async function transferMoney(to: string, amount: number) {
const csrfToken = getCsrfToken();
await fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ to, amount })
});
}
Frontend Security Checklist:
- [ ] No
dangerouslySetInnerHTMLwith user input - [ ] No sensitive tokens in localStorage
- [ ] No hardcoded API keys or secrets
- [ ] CSRF protection for state-changing operations
- [ ] Input validation on all form inputs
- [ ] Output sanitization for user-generated content
- [ ] HTTPS enforced (check in production)
- [ ] No sensitive data in console.log
- [ ] Proper authentication checks on protected routes
- [ ] Secure password input (no autocomplete on sensitive fields)
3.2 React Performance Review
Identify bottlenecks and optimization opportunities:
// β BAD: Unnecessary re-renders
function ProductList({ products }: { products: Product[] }) {
const sortedProducts = products.sort((a, b) => b.price - a.price);
// Re-sorts on every render!
return (
<div>
{sortedProducts.map(p => (
<ProductCard key={p.id} product={p} onUpdate={() => updateProduct(p.id)} />
))}
</div>
);
}
// β
GOOD: Memoized sorting and callbacks
function ProductList({ products }: { products: Product[] }) {
const sortedProducts = useMemo(
() => [...products].sort((a, b) => b.price - a.price),
[products]
);
const handleUpdate = useCallback((id: string) => {
updateProduct(id);
}, []);
return (
<div>
{sortedProducts.map(p => (
<ProductCard key={p.id} product={p} onUpdate={() => handleUpdate(p.id)} />
))}
</div>
);
}
// β BAD: No memoization for expensive child
function ExpensiveChild({ data }: { data: Data }) {
// Complex rendering logic
return <div>{/* ... */}</div>;
}
// β
GOOD: Memoized component
const ExpensiveChild = memo(function ExpensiveChild({ data }: { data: Data }) {
// Complex rendering logic
return <div>{/* ... */}</div>;
});
// β BAD: Memory leak - no cleanup
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
// No cleanup!
}, []);
return <div>{count}</div>;
}
// β
GOOD: Proper cleanup
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>{count}</div>;
}
// β BAD: No lazy loading for large components
import HeavyChart from './HeavyChart';
import HeavyEditor from './HeavyEditor';
// β
GOOD: Lazy loading
const HeavyChart = lazy(() => import('./HeavyChart'));
const HeavyEditor = lazy(() => import('./HeavyEditor'));
function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<HeavyChart />
<HeavyEditor />
</Suspense>
);
}
// β BAD: Inline object/function props
<Child
config={{ theme: 'dark' }}
onUpdate={() => doSomething()}
/>
// β
GOOD: Memoized props
const config = useMemo(() => ({ theme: 'dark' }), []);
const handleUpdate = useCallback(() => doSomething(), []);
<Child config={config} onUpdate={handleUpdate} />
React Performance Checklist:
- [ ] Memoization for expensive calculations (useMemo)
- [ ] Memoized callbacks (useCallback) for child components
- [ ] memo() for expensive child components
- [ ] Proper cleanup in useEffect (intervals, subscriptions)
- [ ] Lazy loading for heavy components
- [ ] Code splitting for routes
- [ ] Virtualization for long lists (react-window)
- [ ] Debouncing for frequent events (search, scroll)
- [ ] Image optimization (next/image, lazy loading)
- [ ] No inline objects/functions as props
- [ ] Proper key usage (unique IDs, not indexes)
- [ ] Avoid deep prop drilling (use Context/Zustand)
3.3 Component Architecture Review
Check for proper separation of concerns:
// β BAD: Everything in one component
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// API call in component
useEffect(() => {
setLoading(true);
fetch('/api/user')
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
// Business logic in component
const fullName = user ? `${user.firstName} ${user.lastName}` : '';
const isAdult = user?.age >= 18;
// Validation in component
const validateEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{fullName}</div>;
}
// β
GOOD: Layered architecture
// api/userApi.ts (Data Access Layer)
export const userApi = {
getUser: async (): Promise<User> => {
const response = await fetch('/api/user');
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
}
};
// hooks/useUser.ts (Business Logic Layer)
export function useUser() {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user'],
queryFn: userApi.getUser
});
const fullName = user ? `${user.firstName} ${user.lastName}` : '';
const isAdult = user?.age >= 18;
return { user, fullName, isAdult, isLoading, error };
}
// utils/validation.ts (Business Logic Layer)
export const emailSchema = z.string().email();
export function validateEmail(email: string): boolean {
return emailSchema.safeParse(email).success;
}
// components/UserProfile.tsx (Presentation Layer)
export function UserProfile() {
const { fullName, isLoading, error } = useUser();
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
return <div className="text-lg font-semibold">{fullName}</div>;
}
Architecture Checklist:
- [ ] Layered architecture (Presentation, Business Logic, Data Access)
- [ ] No API calls directly in components
- [ ] No business logic in components
- [ ] Hooks for reusable logic
- [ ] Zustand/Context for global state
- [ ] React Query for server state
- [ ] No circular dependencies
- [ ] Single Responsibility Principle
- [ ] Components are small and focused (<200 lines)
- [ ] Proper prop types (TypeScript interfaces)
3.4 Type Safety Review
Check for proper TypeScript usage:
// β BAD: Using 'any'
function processData(data: any) {
return data.value;
}
// β
GOOD: Proper types
interface ProcessData {
value: string;
count: number;
}
function processData(data: ProcessData): string {
return data.value;
}
// β BAD: Non-null assertion without justification
function getUser(users: User[], id: string) {
return users.find(u => u.id === id)!.name; // Dangerous!
}
// β
GOOD: Proper null handling
function getUser(users: User[], id: string): string | null {
return users.find(u => u.id === id)?.name ?? null;
}
// β BAD: No runtime validation
function LoginForm() {
const handleSubmit = (data: unknown) => {
// Assuming data structure without validation
login(data);
};
}
// β
GOOD: Runtime validation with Zod
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
type LoginData = z.infer<typeof loginSchema>;
function LoginForm() {
const handleSubmit = (data: unknown) => {
try {
const validatedData = loginSchema.parse(data);
login(validatedData); // Type-safe
} catch (err) {
// Handle validation errors
}
};
}
// β BAD: Implicit any in event handlers
<input onChange={(e) => setValue(e.target.value)} />
// 'e' is implicitly 'any' in some configs
// β
GOOD: Explicit types
<input onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)} />
// β
BETTER: Typed handler
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
<input onChange={handleChange} />
Type Safety Checklist:
- [ ] No 'any' types (except where truly necessary)
- [ ] No non-null assertions (!) without justification
- [ ] Proper interface/type definitions for props
- [ ] Runtime validation with Zod for external data
- [ ] Type guards for discriminated unions
- [ ] Explicit types for event handlers
- [ ] Strict mode enabled in tsconfig.json
- [ ] No implicit any
- [ ] Proper generic usage in hooks
3.5 Accessibility Review
Check WCAG 2.1 AA compliance:
// β BAD: No keyboard support, no ARIA labels
<div onClick={handleClick}>
<input type="text" placeholder="Name" />
<div className="text-red-500">Error message</div>
</div>
// β
GOOD: Semantic HTML, ARIA labels, keyboard support
<form onSubmit={handleSubmit}>
<label htmlFor="name" className="block text-sm font-medium">
Name
</label>
<input
id="name"
type="text"
aria-label="Enter your name"
aria-describedby="name-error"
aria-invalid={hasError}
className="mt-1 block w-full"
/>
{hasError && (
<p id="name-error" role="alert" className="text-red-500 text-sm">
Error message
</p>
)}
<button type="submit" className="mt-4 px-4 py-2 bg-blue-500">
Submit
</button>
</form>
// β BAD: No alt text, poor color contrast
<div className="bg-gray-200 text-gray-300">
<img src="/icon.png" />
Click here
</div>
// β
GOOD: Alt text, proper contrast (WCAG AA)
<div className="bg-gray-900 text-white">
<img src="/icon.png" alt="Settings icon" />
<button className="text-lg font-medium">
Open Settings
</button>
</div>
// β BAD: Custom select without keyboard navigation
function CustomSelect({ options }) {
const [open, setOpen] = useState(false);
return (
<div onClick={() => setOpen(!open)}>
{open && options.map(opt => (
<div onClick={() => selectOption(opt)}>{opt}</div>
))}
</div>
);
}
// β
GOOD: Accessible custom select
function CustomSelect({ options }: { options: string[] }) {
const [open, setOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowDown') {
setSelectedIndex(i => Math.min(i + 1, options.length - 1));
} else if (e.key === 'ArrowUp') {
setSelectedIndex(i => Math.max(i - 1, 0));
} else if (e.key === 'Enter') {
selectOption(options[selectedIndex]);
}
};
return (
<div
role="combobox"
aria-expanded={open}
aria-haspopup="listbox"
tabIndex={0}
onKeyDown={handleKeyDown}
onClick={() => setOpen(!open)}
>
{open && (
<ul role="listbox">
{options.map((opt, index) => (
<li
key={opt}
role="option"
aria-selected={index === selectedIndex}
onClick={() => selectOption(opt)}
>
{opt}
</li>
))}
</ul>
)}
</div>
);
}
Accessibility Checklist:
- [ ] Semantic HTML elements (button, nav, main, etc.)
- [ ] ARIA labels for interactive elements
- [ ] Keyboard navigation support (Tab, Enter, Arrows)
- [ ] Focus management (visible focus states)
- [ ] Color contrast meets WCAG AA (4.5:1 for text)
- [ ] Alt text for all images
- [ ] Form labels properly associated (htmlFor)
- [ ] Error messages use role="alert"
- [ ] No keyboard traps
- [ ] Skip links for main content
- [ ] Headings in logical order (h1, h2, h3)
- [ ] Interactive elements have proper roles
3.6 Responsive Design Review
Check Tailwind/responsive patterns:
// β BAD: Fixed widths, no responsive classes
<div className="w-800 h-600">
<img src="/hero.jpg" className="w-full" />
</div>
// β
GOOD: Responsive Tailwind classes
<div className="w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<img
src="/hero.jpg"
alt="Hero"
className="w-full h-auto object-cover md:h-96 lg:h-[500px]"
/>
</div>
// β BAD: No mobile considerations
<div className="flex gap-8">
<Sidebar />
<MainContent />
</div>
// β
GOOD: Mobile-first responsive layout
<div className="flex flex-col lg:flex-row gap-4 lg:gap-8">
<aside className="w-full lg:w-64">
<Sidebar />
</aside>
<main className="flex-1">
<MainContent />
</main>
</div>
// β BAD: Fixed font sizes
<h1 className="text-32">Title</h1>
// β
GOOD: Responsive typography
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl font-bold">
Title
</h1>
Responsive Design Checklist:
- [ ] Mobile-first approach (base styles for mobile)
- [ ] Proper breakpoints (sm, md, lg, xl, 2xl)
- [ ] Flexible layouts (flex, grid)
- [ ] Responsive images (w-full h-auto, object-fit)
- [ ] Responsive typography (responsive text sizes)
- [ ] Touch-friendly hit areas (min 44x44px)
- [ ] No horizontal scrolling on mobile
- [ ] Tested on multiple screen sizes
3.7 Code Quality Review
Evaluate readability and maintainability:
// β BAD: Unclear names, deeply nested
function p(d) {
if (d) {
if (d.u) {
if (d.u.n) {
if (d.u.n.length > 0) {
return d.u.n;
}
}
}
}
return 'Anonymous';
}
// β
GOOD: Clear names, early returns
function getUserName(data: UserData | null): string {
if (!data?.user?.name) return 'Anonymous';
if (data.user.name.length === 0) return 'Anonymous';
return data.user.name;
}
// β
BETTER: Optional chaining
function getUserName(data: UserData | null): string {
return data?.user?.name || 'Anonymous';
}
// β BAD: Magic numbers and strings
if (user.age > 18 && status === 'active') {
grantAccess();
}
// β
GOOD: Named constants
const MINIMUM_AGE = 18;
const USER_STATUS = {
ACTIVE: 'active',
INACTIVE: 'inactive'
} as const;
if (user.age > MINIMUM_AGE && status === USER_STATUS.ACTIVE) {
grantAccess();
}
// β BAD: Long component with multiple responsibilities
function UserDashboard() {
// 300 lines of code handling:
// - User data fetching
// - Analytics tracking
// - Notification handling
// - UI rendering
}
// β
GOOD: Split into focused components
function UserDashboard() {
return (
<div>
<UserProfile />
<UserAnalytics />
<UserNotifications />
</div>
);
}
Code Quality Checklist:
- [ ] Clear, descriptive names (getUserName, not gn)
- [ ] No magic numbers or strings (use constants)
- [ ] Early returns instead of deep nesting
- [ ] Small functions/components (<100 lines)
- [ ] Single Responsibility Principle
- [ ] DRY (Don't Repeat Yourself)
- [ ] No commented-out code
- [ ] No console.log in production code
- [ ] Consistent code style
- [ ] Proper error handling
3.8 React Hooks Best Practices
// β BAD: Missing dependencies
useEffect(() => {
fetchData(userId, filter);
}, []); // userId and filter are missing!
// β
GOOD: All dependencies included
useEffect(() => {
fetchData(userId, filter);
}, [userId, filter]);
// β BAD: Object dependency (recreated every render)
const options = { sort: 'asc', limit: 10 };
useEffect(() => {
fetchData(options);
}, [options]); // Will run every render!
// β
GOOD: Memoized object
const options = useMemo(() => ({
sort: 'asc',
limit: 10
}), []);
useEffect(() => {
fetchData(options);
}, [options]);
// β BAD: Stale closure
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1); // Uses stale count!
}, 1000);
return () => clearInterval(interval);
}, []); // Empty deps, count is stale
return <div>{count}</div>;
}
// β
GOOD: Functional update
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1); // Uses current count
}, 1000);
return () => clearInterval(interval);
}, []); // Empty deps OK now
return <div>{count}</div>;
}
Step 4: Provide Structured Feedback
Format review results clearly:
## Code Review: [Component/Feature Name]
### Summary
[Brief overall assessment: Excellent/Good/Needs Improvement/Critical Issues]
### Critical Issues π¨
1. **[Issue Title]** - [file:line]
- **Problem**: [Description]
- **Impact**: [Security/Performance/Accessibility]
- **Fix**:
```typescript
// Suggested fix
High Priority β οΈ
[Same format]
Medium Priority β‘
[Same format]
Low Priority π‘
[Same format]
What's Good β
- [Highlight positive aspects]
- [Good practices used]
Recommendations
- [Actionable improvement]
- [Actionable improvement]
Overall Score
- Security: 8/10
- Performance: 7/10
- Code Quality: 9/10
- Accessibility: 6/10
- Type Safety: 8/10
Overall: 7.6/10
## Integration with Other Skills
### With feature-builder
- Review complete features (UI + business logic + API)
- Ensure layered architecture is properly implemented
### With react-component-generator
- Review generated components
- Check template usage and customization
### With ui-analyzer
- Review UI code generated from design screenshots
- Verify responsive design implementation
## Best Practices
1. **Be Constructive**: Always provide actionable fixes with code examples
2. **Prioritize**: Focus on critical issues (security, accessibility) first
3. **Explain Why**: Help user understand the reasoning and impact
4. **Show Before/After**: Provide clear code examples
5. **Be Specific**: Reference exact lines and files
6. **Balance**: Highlight what's good too
7. **Be Practical**: Consider project constraints and deadlines
8. **Educate**: Explain React/frontend concepts when needed
## Severity Levels
- π¨ **CRITICAL**: Security vulnerabilities (XSS, CSRF), data exposure, crashes
- β οΈ **HIGH**: Performance issues (memory leaks), accessibility violations, broken UX
- β‘ **MEDIUM**: Code quality, maintainability, missing TypeScript types
- π‘ **LOW**: Code style, documentation, minor optimizations
## Reference Files
- `references/frontend-security.md` - Frontend security best practices
- `references/react-patterns.md` - React patterns and anti-patterns
- `references/accessibility-guide.md` - WCAG compliance guide
This skill enables thorough, professional frontend code reviews that improve code quality, security, and user experience!