1kalin/afrexai-react-production/SKILL.md
Complete methodology for building production-grade React applications with architecture decisions, component design, state management, performance optimization, testing, and deployment.
npx skillsauth add openclaw/skills afrexai-react-productionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Complete methodology for building production-grade React applications. Covers architecture decisions, component design, state management, performance optimization, testing, and deployment — not just API reference, but engineering methodology with decision frameworks, templates, and scoring systems.
any types in production code (+2)project:
name: ""
type: "" # spa | ssr | hybrid | static
framework: "" # next | remix | vite-spa | astro
scale: "" # small (<20 routes) | medium (20-100) | large (100+)
team_size: "" # solo | small (2-5) | medium (6-15) | large (15+)
current_state:
react_version: "" # 18 | 19
typescript: true
router: "" # react-router | next-app | tanstack-router
state_management: "" # useState | zustand | jotai | redux | tanstack-query
styling: "" # tailwind | css-modules | styled-components | vanilla-extract
testing: "" # vitest | jest | playwright | cypress
ci_cd: "" # github-actions | gitlab-ci | vercel
pain_points: []
goals: []
| Factor | Vite SPA | Next.js | Remix | Astro | |--------|----------|---------|-------|-------| | SEO needed | ❌ | ✅ Best | ✅ Good | ✅ Best | | Dashboard/app | ✅ Best | ✅ Good | ✅ Good | ❌ | | Content-heavy | ❌ | ✅ Good | ✅ Good | ✅ Best | | Team familiarity | ✅ Simple | ⚠️ Learning curve | ⚠️ Web standards | ⚠️ Islands | | Deployment | Anywhere | Vercel optimal | Anywhere | Anywhere | | Bundle size | You control | Framework overhead | Smaller | Minimal JS |
Decision rules:
src/
├── app/ # Routes/pages (framework-specific)
├── features/ # Feature modules (THE core pattern)
│ ├── auth/
│ │ ├── components/ # Feature-specific components
│ │ ├── hooks/ # Feature-specific hooks
│ │ ├── api/ # API calls & types
│ │ ├── utils/ # Feature utilities
│ │ ├── types.ts # Feature types
│ │ └── index.ts # Public API (barrel export)
│ ├── dashboard/
│ └── settings/
├── shared/ # Cross-feature shared code
│ ├── components/ # Generic UI components
│ │ ├── ui/ # Primitives (Button, Input, Card)
│ │ └── layout/ # Layout components
│ ├── hooks/ # Generic hooks
│ ├── lib/ # Utilities, constants
│ └── types/ # Global types
├── providers/ # Context providers
└── styles/ # Global styles
Components: PascalCase.tsx (UserProfile.tsx)
Hooks: useCamelCase.ts (useAuth.ts)
Utilities: camelCase.ts (formatCurrency.ts)
Types: PascalCase.ts (User.ts) or types.ts
Constants: SCREAMING_SNAKE.ts (API_ENDPOINTS.ts)
Test files: *.test.tsx (UserProfile.test.tsx)
Story files: *.stories.tsx (Button.stories.tsx)
// 1. Imports (grouped: react → third-party → internal → types → styles)
import { useState, useCallback, memo } from 'react'
import { clsx } from 'clsx'
import { Button } from '@/shared/components/ui'
import type { User } from '../types'
// 2. Types (exported for reuse)
export interface UserCardProps {
user: User
onEdit?: (id: string) => void
variant?: 'compact' | 'full'
className?: string
}
// 3. Component (named export, not default)
export const UserCard = memo(function UserCard({
user,
onEdit,
variant = 'full',
className,
}: UserCardProps) {
// 4. Hooks first
const [isExpanded, setIsExpanded] = useState(false)
// 5. Derived state (no useEffect for derived!)
const displayName = `${user.firstName} ${user.lastName}`
// 6. Handlers (useCallback for passed-down refs)
const handleEdit = useCallback(() => {
onEdit?.(user.id)
}, [onEdit, user.id])
// 7. Early returns for edge cases
if (!user) return null
// 8. JSX (max 50 lines)
return (
<div className={clsx('rounded-lg border p-4', className)}>
<h3>{displayName}</h3>
{variant === 'full' && <p>{user.bio}</p>}
{onEdit && <Button onClick={handleEdit}>Edit</Button>}
</div>
)
})
1. Compound Components (for related UI groups)
// Usage: <Tabs><Tabs.List><Tabs.Tab>A</Tabs.Tab></Tabs.List><Tabs.Panel>...</Tabs.Panel></Tabs>
const TabsContext = createContext<TabsContextType | null>(null)
export function Tabs({ children, defaultValue }: TabsProps) {
const [activeTab, setActiveTab] = useState(defaultValue)
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
{children}
</TabsContext.Provider>
)
}
Tabs.List = TabsList
Tabs.Tab = TabsTab
Tabs.Panel = TabsPanel
2. Render Props (for flexible rendering logic)
export function DataList<T>({ items, renderItem, renderEmpty }: DataListProps<T>) {
if (items.length === 0) return renderEmpty?.() ?? <EmptyState />
return <ul>{items.map((item, i) => <li key={i}>{renderItem(item)}</li>)}</ul>
}
3. Higher-Order Components (for cross-cutting concerns — use sparingly)
export function withAuth<P>(Component: ComponentType<P>) {
return function AuthenticatedComponent(props: P) {
const { user, isLoading } = useAuth()
if (isLoading) return <Spinner />
if (!user) return <Navigate to="/login" />
return <Component {...props} />
}
}
Is it server data (from API)?
├─ YES → TanStack Query (or SWR) — NEVER Redux/Zustand for server state
│
└─ NO → Is it shared across features?
├─ YES → Is it complex with many actions?
│ ├─ YES → Zustand (or Redux Toolkit if team knows it)
│ └─ NO → Jotai (atomic) or Zustand (simple store)
│
└─ NO → Is it shared within a feature?
├─ YES → Context + useReducer (or Zustand feature store)
└─ NO → useState / useReducer (component-local)
| Tool | Best For | Bundle | Learning | Team Size | |------|----------|--------|----------|-----------| | useState | Component-local | 0 KB | None | Any | | useReducer | Complex local state | 0 KB | Low | Any | | Context | Feature-scoped, low-frequency | 0 KB | Low | Any | | Zustand | Global client state | 1.1 KB | Low | Any | | Jotai | Atomic derived state | 3.4 KB | Medium | Small-Med | | TanStack Query | Server state | 12 KB | Medium | Any | | Redux Toolkit | Complex global + middleware | 11 KB | High | Large |
// api/users.ts — query key factory pattern
export const userKeys = {
all: ['users'] as const,
lists: () => [...userKeys.all, 'list'] as const,
list: (filters: Filters) => [...userKeys.lists(), filters] as const,
details: () => [...userKeys.all, 'detail'] as const,
detail: (id: string) => [...userKeys.details(), id] as const,
}
// hooks/useUsers.ts
export function useUsers(filters: Filters) {
return useQuery({
queryKey: userKeys.list(filters),
queryFn: () => fetchUsers(filters),
staleTime: 5 * 60 * 1000, // 5 min
placeholderData: keepPreviousData,
})
}
export function useUpdateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: updateUser,
onMutate: async (newUser) => {
// Optimistic update
await queryClient.cancelQueries({ queryKey: userKeys.detail(newUser.id) })
const previous = queryClient.getQueryData(userKeys.detail(newUser.id))
queryClient.setQueryData(userKeys.detail(newUser.id), newUser)
return { previous }
},
onError: (err, newUser, context) => {
queryClient.setQueryData(userKeys.detail(newUser.id), context?.previous)
},
onSettled: (data, err, variables) => {
queryClient.invalidateQueries({ queryKey: userKeys.detail(variables.id) })
queryClient.invalidateQueries({ queryKey: userKeys.lists() })
},
})
}
// stores/useUIStore.ts — thin, focused stores
interface UIStore {
sidebarOpen: boolean
theme: 'light' | 'dark' | 'system'
toggleSidebar: () => void
setTheme: (theme: UIStore['theme']) => void
}
export const useUIStore = create<UIStore>()(
persist(
(set) => ({
sidebarOpen: true,
theme: 'system',
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
setTheme: (theme) => set({ theme }),
}),
{ name: 'ui-preferences' }
)
)
// Usage: const theme = useUIStore((s) => s.theme) — always use selectors!
useStore(s => s.field) not useStore()// hooks/useDebounce.ts
export function useDebounce<T>(value: T, delayMs: number = 300): T {
const [debouncedValue, setDebouncedValue] = useState(value)
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delayMs)
return () => clearTimeout(timer)
}, [value, delayMs])
return debouncedValue
}
| Hook | Purpose | When to Use |
|------|---------|-------------|
| useDebounce | Debounce value changes | Search inputs, resize |
| useMediaQuery | Responsive breakpoints | Conditional rendering |
| useLocalStorage | Persistent local state | Preferences, drafts |
| useIntersection | Viewport detection | Lazy load, infinite scroll |
| usePrevious | Track previous value | Animations, comparisons |
| useClickOutside | Detect outside clicks | Dropdowns, modals |
| useEventListener | Safe event binding | Keyboard, scroll, resize |
| useToggle | Boolean state toggle | Modals, accordions |
useUserSearch not useEverythinguseDebounce(value, { delay: 300 }) scales betterrenderHook from testing-library{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
// 1. Discriminated unions for state machines
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
// 2. Polymorphic components
type ButtonProps<C extends ElementType = 'button'> = {
as?: C
variant?: 'primary' | 'secondary'
} & ComponentPropsWithoutRef<C>
export function Button<C extends ElementType = 'button'>({
as,
variant = 'primary',
...props
}: ButtonProps<C>) {
const Component = as || 'button'
return <Component {...props} />
}
// 3. Branded types for IDs
type UserId = string & { __brand: 'UserId' }
type PostId = string & { __brand: 'PostId' }
// 4. Zod for runtime validation
const userSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
role: z.enum(['admin', 'user', 'viewer']),
})
type User = z.infer<typeof userSchema>
any — use unknown and narrow, or generics{ status: 'success'; data: T } not { data?: T; error?: Error }userId being passed where postId expectedconfig satisfies Config preserves inference; as Config lies| Metric | Target | Measurement | |--------|--------|-------------| | First Contentful Paint | < 1.8s | Lighthouse | | Largest Contentful Paint | < 2.5s | Lighthouse | | Interaction to Next Paint | < 200ms | Lighthouse | | Cumulative Layout Shift | < 0.1 | Lighthouse | | Bundle size (gzipped) | < 200 KB | webpack-bundle-analyzer | | JS execution (main thread) | < 3s | Chrome DevTools |
| Priority | Technique | Impact | Effort | |----------|-----------|--------|--------| | P0 | Code splitting (route-based) | 🔴 High | Low | | P0 | Image optimization (next/image, srcset) | 🔴 High | Low | | P1 | Tree shaking (named imports) | 🟡 Medium | Low | | P1 | Virtualization for long lists | 🟡 Medium | Medium | | P1 | Debounce expensive operations | 🟡 Medium | Low | | P2 | React.memo on expensive components | 🟢 Low-Med | Low | | P2 | useMemo/useCallback for expensive calculations | 🟢 Low-Med | Low | | P3 | Web Workers for heavy computation | 🟢 Low | High |
// 1. Route-based (automatic with Next.js, manual with React Router)
const Dashboard = lazy(() => import('./features/dashboard'))
const Settings = lazy(() => import('./features/settings'))
// 2. Component-based (heavy components)
const Chart = lazy(() => import('./components/Chart'))
const MarkdownEditor = lazy(() =>
import('./components/MarkdownEditor').then(m => ({ default: m.MarkdownEditor }))
)
// 3. Library-based (heavy third-party)
const { PDFViewer } = await import('@react-pdf/renderer')
// With React Compiler enabled, manual memo/useMemo/useCallback become unnecessary
// The compiler auto-memoizes. Remove manual optimizations:
// ❌ const memoized = useMemo(() => expensiveCalc(data), [data])
// ✅ const memoized = expensiveCalc(data) // compiler handles it
// Enable in babel config:
// plugins: [['babel-plugin-react-compiler', {}]]
style={{ color: 'red' }} rerenders always<Layout><ExpensiveChild /></Layout>Math.random()// Three levels of error boundaries:
// 1. App-level (catches everything, shows full-page error)
// 2. Feature-level (isolates feature failures)
// 3. Component-level (for risky widgets — charts, third-party)
// Modern error boundary with react-error-boundary
import { ErrorBoundary, FallbackProps } from 'react-error-boundary'
function FeatureErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div role="alert" className="rounded-lg border-red-200 bg-red-50 p-4">
<h3>Something went wrong</h3>
<pre className="text-sm text-red-600">{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}
// Usage:
<ErrorBoundary FallbackComponent={FeatureErrorFallback} onReset={() => queryClient.clear()}>
<DashboardFeature />
</ErrorBoundary>
onError / error states| Library | Best For | Bundle | Renders | |---------|----------|--------|---------| | React Hook Form | Most forms | 9 KB | Minimal (uncontrolled) | | Formik | Simple forms | 13 KB | Every keystroke | | TanStack Form | Type-safe complex | 5 KB | Controlled | | Native | 1-2 field forms | 0 KB | You control |
Default recommendation: React Hook Form + Zod
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Min 8 characters'),
role: z.enum(['admin', 'user']),
})
type FormData = z.infer<typeof schema>
export function LoginForm({ onSubmit }: { onSubmit: (data: FormData) => void }) {
const form = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { email: '', password: '', role: 'user' },
})
return (
<form onSubmit={form.handleSubmit(onSubmit)} noValidate>
<label htmlFor="email">Email</label>
<input id="email" type="email" {...form.register('email')} aria-invalid={!!form.formState.errors.email} />
{form.formState.errors.email && (
<p role="alert">{form.formState.errors.email.message}</p>
)}
{/* ... more fields */}
<button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? 'Signing in...' : 'Sign in'}
</button>
</form>
)
}
| Level | Tool | Coverage Target | What to Test | |-------|------|-----------------|-------------| | Unit | Vitest | 80% business logic | Hooks, utilities, reducers | | Component | Testing Library | Key user flows | Rendering, interactions, a11y | | Integration | Testing Library | Feature flows | Multi-component workflows | | E2E | Playwright | Critical paths | Auth, checkout, core flows | | Visual | Chromatic/Percy | UI components | Regression detection |
// Component test (Testing Library philosophy: test behavior, not implementation)
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
describe('UserCard', () => {
it('calls onEdit when edit button clicked', async () => {
const user = userEvent.setup()
const onEdit = vi.fn()
render(<UserCard user={mockUser} onEdit={onEdit} />)
await user.click(screen.getByRole('button', { name: /edit/i }))
expect(onEdit).toHaveBeenCalledWith(mockUser.id)
})
it('does not render edit button when onEdit not provided', () => {
render(<UserCard user={mockUser} />)
expect(screen.queryByRole('button', { name: /edit/i })).not.toBeInTheDocument()
})
})
getByRole > getByTestId > getByTextuserEvent.click simulates real interaction<button> not <div onClick>, <nav> not <div class="nav"><img> has descriptive alt (or alt="" if decorative)aria-label for icon-only buttons, aria-describedby for hintsaria-live="polite" for dynamic content (toasts, form errors)prefers-reduced-motion for animationsvitest-axe or @axe-core/playwright)| Layer | Recommendation | Alternative | |-------|---------------|-------------| | Framework | Next.js 15 | Remix, Vite SPA | | Language | TypeScript (strict) | — | | Styling | Tailwind CSS v4 | CSS Modules | | Components | shadcn/ui | Radix, Headless UI | | State (server) | TanStack Query v5 | SWR | | State (client) | Zustand | Jotai | | Forms | React Hook Form + Zod | TanStack Form | | Testing | Vitest + Testing Library | Jest | | E2E | Playwright | Cypress | | Linting | Biome | ESLint + Prettier | | Auth | Auth.js (NextAuth) | Clerk, Lucia | | Database | Drizzle ORM | Prisma | | Deployment | Vercel | Cloudflare, Fly.io | | Monitoring | Sentry | Datadog |
| Dimension | Weight | What to Score | |-----------|--------|--------------| | Architecture | 20% | Structure, separation, patterns | | Type safety | 15% | Strict TS, zero any, Zod boundaries | | Performance | 15% | Core Web Vitals, bundle size | | Testing | 15% | Coverage, quality, pyramid | | Accessibility | 10% | WCAG AA, keyboard, screen reader | | State management | 10% | Right tool, no prop drilling | | Error handling | 10% | Boundaries, user-friendly, monitoring | | Developer experience | 5% | Linting, formatting, CI speed |
Grading: 90+ World-class | 75-89 Production-ready | 60-74 Needs work | <60 Tech debt crisis
| # | Mistake | Fix | |---|---------|-----| | 1 | useEffect for derived state | Compute inline or useMemo | | 2 | Prop drilling 5+ levels deep | Context, Zustand, or composition | | 3 | Fetching in useEffect | TanStack Query or framework loaders | | 4 | Default exports everywhere | Named exports for refactoring safety | | 5 | Testing implementation details | Test behavior with Testing Library | | 6 | Giant components (500+ lines) | Extract hooks and sub-components | | 7 | No error boundaries | Add at app, feature, and widget level | | 8 | Redux for server state | TanStack Query for API data | | 9 | Ignoring a11y until the end | Build accessible from day 1 | | 10 | No TypeScript strict mode | Enable strict, fix all errors |
This skill gives you the methodology. For industry-specific implementation patterns, grab an AfrexAI Context Pack ($47):
👉 Browse all 10 packs: https://afrexai-cto.github.io/context-packs/
afrexai-nextjs-production — Next.js production engineeringafrexai-vibe-coding — AI-assisted development methodologyafrexai-technical-seo — SEO for React SPAs and SSRafrexai-test-automation-engineering — Complete testing strategyafrexai-ui-design-system — Design system architecturetools
Use when the user wants to connect to, test, or use the McDonalds service at mcp.mcd.cn, including checking authentication, probing MCP endpoints, listing tools, or calling McDonalds MCP tools through a reusable local CLI.
development
Web scraping platform — Twitter/X data, Vinted marketplace, and general web scraping API
development
SlowMist AI Agent Security Review — comprehensive security framework for skills, repositories, URLs, on-chain addresses, and products (Claude Code version)
data-ai
去除中文文本中的 AI 写作痕迹,使其读起来自然。基于维基百科 AI 写作特征指南,检测 24 种 AI 模式。触发词:humanizer-cn、去除 AI 痕迹、去除 AI 写作痕迹、中文文本人性化。