🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

React Production Engineering

by @1kalin

Complete methodology for building production-grade React applications with architecture decisions, component design, state management, performance optimizati...

Versionv1.0.0
Downloads1,117
Installs4
TERMINAL
clawhub install afrexai-react-production

πŸ“– About This Skill


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.

React Production Engineering

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.

Phase 1: Architecture Assessment

Quick Health Check (score /16)

  • [ ] Component tree depth < 6 levels (+2)
  • [ ] No prop drilling past 2 levels (+2)
  • [ ] Bundle size < 200KB gzipped (+2)
  • [ ] LCP < 2.5s on 4G (+2)
  • [ ] Test coverage > 70% on business logic (+2)
  • [ ] Zero any types in production code (+2)
  • [ ] No direct DOM manipulation (+2)
  • [ ] Consistent error boundaries (+2)
  • Architecture Brief

    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: []
    

    Framework Selection Decision Matrix

    | 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: 1. Dashboard/internal tool with no SEO β†’ Vite SPA 2. Marketing + app hybrid β†’ Next.js 3. Content-first with some interactivity β†’ Astro 4. Web-standards-first, nested layouts β†’ Remix 5. Default for most SaaS products β†’ Next.js


    Phase 2: Project Structure & Conventions

    Recommended Feature-Based Structure

    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
    

    7 Structure Rules

    1. Feature isolation β€” features/ never import from other features directly; use shared/ or events 2. Barrel exports β€” every feature has index.ts that defines its public API 3. Colocation β€” tests, stories, and styles live next to their component 4. Max file size β€” 300 lines. If bigger, split 5. Max component size β€” 50 lines of JSX. If bigger, extract 6. No circular deps β€” enforce with eslint-plugin-import 7. Types colocated β€” feature types in feature, shared types in shared/types

    Naming Conventions

    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)
    


    Phase 3: Component Design Patterns

    Component Anatomy Template

    // 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 (

    {displayName}

    {variant === 'full' &&

    {user.bio}

    } {onEdit && }
    ) })

    Component Composition Patterns

    1. Compound Components (for related UI groups)

    // Usage: A...
    const TabsContext = createContext(null)

    export function Tabs({ children, defaultValue }: TabsProps) { const [activeTab, setActiveTab] = useState(defaultValue) return ( {children} ) } Tabs.List = TabsList Tabs.Tab = TabsTab Tabs.Panel = TabsPanel

    2. Render Props (for flexible rendering logic)

    export function DataList({ items, renderItem, renderEmpty }: DataListProps) {
      if (items.length === 0) return renderEmpty?.() ?? 
      return 
      {items.map((item, i) =>
    • {renderItem(item)}
    • )}
    }

    3. Higher-Order Components (for cross-cutting concerns β€” use sparingly)

    export function withAuth

    (Component: ComponentType

    ) { return function AuthenticatedComponent(props: P) { const { user, isLoading } = useAuth() if (isLoading) return if (!user) return return } }

    10 Component Rules

    1. One component per file β€” always 2. Named exports β€” never default exports (refactoring safety) 3. Props interface β€” always explicit, always exported 4. No business logic in components β€” extract to hooks 5. No inline styles β€” use Tailwind classes or CSS modules 6. No string refs β€” useRef only 7. No index as key β€” use stable identifiers 8. Memo strategically β€” not everywhere, only for expensive renders 9. Children over props β€” prefer composition over configuration 10. Accessible by default β€” semantic HTML, ARIA when needed


    Phase 4: State Management Decision Framework

    State Type Decision Tree

    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)
    

    State Management Comparison

    | 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 |

    Server State with TanStack Query

    // 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() }) }, }) }

    Client State with Zustand

    // 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()( 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!

    5 State Management Rules

    1. Server state β‰  client state β€” never mix them in the same store 2. Smallest scope possible β€” useState > Context > Zustand > Redux 3. No useEffect for derived state β€” use useMemo or compute inline 4. Selectors always β€” useStore(s => s.field) not useStore() 5. URL is state β€” search params, filters, pagination β†’ URL, not React state


    Phase 5: Hooks Engineering

    Custom Hook Template

    // hooks/useDebounce.ts
    export function useDebounce(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 }

    Essential Custom Hooks Library

    | 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 |

    Hook Rules (beyond React's rules)

    1. One concern per hook β€” useUserSearch not useEverything 2. Return tuple or object β€” tuple for 1-2 values, object for 3+ 3. Accept options object β€” useDebounce(value, { delay: 300 }) scales better 4. Handle cleanup β€” every subscription/timer needs cleanup in useEffect return 5. No hooks in conditions β€” extract conditional logic into the hook body 6. Test hooks independently β€” use renderHook from testing-library


    Phase 6: TypeScript Integration

    Strict Configuration

    {
      "compilerOptions": {
        "strict": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitOverride": true,
        "exactOptionalPropertyTypes": true,
        "forceConsistentCasingInFileNames": true,
        "paths": {
          "@/*": ["./src/*"]
        }
      }
    }
    

    Essential Type Patterns

    // 1. Discriminated unions for state machines
    type AsyncState =
      | { status: 'idle' }
      | { status: 'loading' }
      | { status: 'success'; data: T }
      | { status: 'error'; error: Error }

    // 2. Polymorphic components type ButtonProps = { as?: C variant?: 'primary' | 'secondary' } & ComponentPropsWithoutRef

    export function Button({ as, variant = 'primary', ...props }: ButtonProps) { const Component = as || 'button' return }

    // 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

    5 TypeScript Rules

    1. Zero any β€” use unknown and narrow, or generics 2. Zod at boundaries β€” validate all external data (API, forms, URL params) 3. Discriminated unions over optional fields β€” { status: 'success'; data: T } not { data?: T; error?: Error } 4. Branded types for IDs β€” prevent userId being passed where postId expected 5. Satisfies over as β€” config satisfies Config preserves inference; as Config lies


    Phase 7: Performance Optimization

    Performance Budget

    | 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 |

    Optimization Priority Stack

    | 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 |

    Code Splitting Patterns

    // 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')

    React Compiler (React 19+)

    // 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', {}]]

    Rendering Performance Rules

    1. Never create components inside components β€” define at module level 2. Never create objects/arrays in JSX β€” style={{ color: 'red' }} rerenders always 3. Children as props prevent rerender β€” 4. Key must be stable and unique β€” not index, not Math.random() 5. Avoid context value churn β€” memoize provider value or split contexts 6. Profile before optimizing β€” React DevTools Profiler, not guesswork


    Phase 8: Error Handling & Resilience

    Error Boundary Architecture

    // 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 (

    Something went wrong

    {error.message}
    ) }

    // Usage: queryClient.clear()}>

    Error Handling Checklist

  • [ ] App-level error boundary wrapping entire app
  • [ ] Feature-level boundaries for each major feature
  • [ ] API errors handled in TanStack Query's onError / error states
  • [ ] Form validation errors shown inline (not alerts)
  • [ ] 404 page for unknown routes
  • [ ] Offline detection and graceful degradation
  • [ ] Error reporting to monitoring (Sentry, etc.)
  • [ ] User-friendly error messages (no stack traces in production)

  • Phase 9: Forms & Validation

    Form Library Decision

    | 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

    Form Pattern

    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

    export function LoginForm({ onSubmit }: { onSubmit: (data: FormData) => void }) { const form = useForm({ resolver: zodResolver(schema), defaultValues: { email: '', password: '', role: 'user' }, })

    return (

    {form.formState.errors.email && (

    {form.formState.errors.email.message}

    )} {/* ... more fields */}
    ) }


    Phase 10: Testing Strategy

    Test Pyramid for React

    | 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 |

    Testing Patterns

    // 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()

    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() expect(screen.queryByRole('button', { name: /edit/i })).not.toBeInTheDocument() }) })

    7 Testing Rules

    1. Test behavior, not implementation β€” never test state directly or useEffect 2. Use accessible queries β€” getByRole > getByTestId > getByText 3. User events over fireEvent β€” userEvent.click simulates real interaction 4. One assertion per concept β€” not one per test, but focused assertions 5. Mock at boundaries β€” API calls, not internal functions 6. No snapshot tests β€” they break on every change and test nothing meaningful 7. Arrange-Act-Assert β€” clear structure in every test


    Phase 11: Accessibility (a11y)

    10-Point Accessibility Checklist

    1. Semantic HTML β€”