vibeeval/frontend-patterns
React, Next.js, TypeScript frontend patterns - component design, state management, performance
npx skills add https://github.com/vibeeval/vibecosystem --skill frontend-patterns
// DOGRU: Composition
function Card({ children, header }: { children: ReactNode; header: ReactNode }) {
return (
<div className="card">
<div className="card-header">{header}</div>
<div className="card-body">{children}</div>
</div>
);
}
// Kullanim
<Card header={<h2>Title</h2>}>
<p>Content here</p>
</Card>
// Container: Data fetching + logic
function UserListContainer() {
const { data, isLoading } = useQuery(['users'], fetchUsers);
if (isLoading) return <Skeleton />;
return <UserList users={data} />;
}
// Presenter: Pure rendering
function UserList({ users }: { users: User[] }) {
return (
<ul>
{users.map(u => <UserItem key={u.id} user={u} />)}
</ul>
);
}
| Cozum | Ne Zaman | Karmasiklik |
|-------|----------|-------------|
| useState | Local component state | Dusuk |
| useReducer | Complex local state | Orta |
| Context | Theme, auth, locale | Dusuk-Orta |
| Zustand | Global client state | Orta |
| React Query | Server state | Orta |
| URL params | Navigation state | Dusuk |
// React.memo: Expensive render onleme
const ExpensiveList = React.memo(({ items }: { items: Item[] }) => (
<ul>{items.map(i => <ListItem key={i.id} item={i} />)}</ul>
));
// useMemo: Expensive hesaplama cache
const filtered = useMemo(
() => items.filter(i => i.status === status),
[items, status]
);
// useCallback: Referans stabilitesi
const handleClick = useCallback((id: string) => {
setSelected(id);
}, []);
| Feature | Ne Zaman |
|---------|----------|
| Server Component | Data fetch, no interactivity |
| Client Component | onClick, useState, useEffect |
| Route Handler | API endpoint |
| Middleware | Auth, redirect, rewrite |
| Streaming | Buyuk data, progressive rendering |
// Theme config: globals.css'te CSS variable kullan
// shadcn/ui dark mode: class strategy (Tailwind darkMode: 'class')
// Component override: cn() ile extend et, fork etme
import { Button } from '@/components/ui/button'
<Button variant="outline" className={cn('custom-class', className)} />
// Compose, override etme:
function SubmitButton({ children, ...props }: ButtonProps) {
return <Button type="submit" variant="default" {...props}>{children}</Button>
}
// A11y: shadcn radix tabanli, otomatik ARIA ama kontrol et
// Dark mode: CSS variable'lar otomatik switch eder
| Metrik | Esik | Olcum |
|--------|------|-------|
| LCP (Largest Contentful Paint) | < 2.5s | Core Web Vital |
| CLS (Cumulative Layout Shift) | < 0.1 | Core Web Vital |
| INP (Interaction to Next Paint) | < 200ms | Core Web Vital |
| FCP (First Contentful Paint) | < 1.8s | Lighthouse |
| TTI (Time to Interactive) | < 3.8s | Lighthouse |
| Total JS bundle | < 200KB gzipped | Build analiz |
React.memo sadece gercekten expensive render'lardauseMemo/useCallback sadece referans stability gerektigindeReact.lazy() + Suspense route bazlinext/image, lazy loading, WebP/AVIF@tanstack/virtual@next/bundle-analyzer ile kontrol<Link prefetch> kritik navigation'larda10. Server Component: Default RSC, client sadece interactive icin
| Anti-Pattern | Dogru Yol |
|-------------|-----------|
| Prop drilling (5+ level) | Context veya state library |
| useEffect for derived state | useMemo kullan |
| Index as key | Unique ID kullan |
| Premature optimization | Profiler ile olc, sonra optimize et |
Take vibeeval/frontend-patterns from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.