with one click
afrexai-react-production
// Complete methodology for building production-grade React applications with architecture decisions, component design, state management, performance optimization, testing, and deployment.
// Complete methodology for building production-grade React applications with architecture decisions, component design, state management, performance optimization, testing, and deployment.
Writers Room Story Engine mega-skill. Creates, structures, drafts, and revises compelling standalone stories from zero using premise generation, audience engagement, thematic design, character arcs, Story Spine structure, causal beats, worldbuilding in service of story, scene construction, and revision diagnostics. Use when developing short stories, standalone fiction, scripts, or narrative concepts from scratch or when improving weak drafts.
Deliver real-time websocket updates for intent, quote, negotiation, order, and payment events. Use when implementing push channels, subscription authorization, and connection/session lifecycle for market clients.
Adaptive RAG 引擎 — 从线性检索到自主认知循环。集成胶囊预筛选、智能路由、CRAG纠错、L3校验。当需要搜索记忆/检索信息/回答复杂问题时触发。关键词:RAG、检索、记忆搜索、向量检索、Agentic RAG、CRAG。
Adopt a virtual Drift AI-native pet at animalhouse.ai. Wanders between states. Location is never the same twice. Feeding every 6 hours. Common tier creature.
Control Android over LAN without USB, ADB, or root.
Proactive agile metrics and team health analysis for Trello and Jira boards. Computes cycle time, throughput, WIP, sprint burndown, aging work items, and blocker detection. Use when: analyzing sprint/board health, generating agile reports, checking team flow metrics, identifying bottlenecks, computing cycle time or throughput, reviewing WIP limits, detecting stale or blocked items, or creating sprint health summaries. Triggers on: how is the sprint going, board health, cycle time, throughput, burndown, agile metrics, team flow, WIP, blocked items, sprint report, agile health check.
| name | afrexai-react-production |
| description | Complete methodology for building production-grade React applications with architecture decisions, component design, state management, performance optimization, testing, and deployment. |
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 architecture