Claude
Skills
Sign in
Back

react-next-modern

Included with Lifetime
$97 forever

Enforce modern React 19 and Next.js App Router patterns - server-first data fetching, minimal useEffect, Server Components, Server Actions, and form hooks. Use when reviewing React/Next.js code, migrating legacy patterns, or building new features with App Router.

Web Dev

What this skill does


# React and Next.js Modern Best Practices (2025)

## Purpose

This skill enforces the architectural shift from client-side synchronization to **async-native** patterns using React 19 and Next.js App Router. The goal is to eliminate unnecessary `useEffect` usage, move data fetching to the server, and leverage modern primitives like Server Components and Server Actions.

## Core Philosophy: The Paradigm Shift

**Legacy Era (Pre-2024)**: Client components mount, then sync with server via `useEffect`
**Modern Era (2025)**: Server components execute async logic, stream results to client

This shift eliminates:
- Waterfall network requests
- Race conditions in effects
- Layout shifts from loading states
- Manual state management for async operations
- Prop drilling for data

## When to Use This Skill

Activate when:
- Writing new React/Next.js code
- Migrating from Pages Router to App Router
- Refactoring `useEffect`-heavy components
- Implementing forms and mutations
- Reviewing data fetching patterns
- Setting up authentication or authorization
- Optimizing React performance

## How to Use This Skill

When reviewing React/Next.js code:

1. **Identify the stack**
   - Check `package.json` for Next.js version
   - Detect if `app/` directory exists (App Router)
   - Note if React 19 is being used

2. **Scan for code smells**
   - Search for: `useEffect`, `useState`, `useCallback`, `useMemo`
   - Look for: `fetch` in Client Components, API routes used for internal data
   - Find: `getServerSideProps`, `getStaticProps` (legacy patterns)

3. **Apply the checklists below**
   - For each issue, propose a modern alternative
   - Show before/after code examples

4. **Prioritize fixes**
   - First: Correctness and side effect bugs
   - Second: Architecture and data fetching
   - Third: Performance and ergonomics

---

## Checklist 1: useEffect - The "Escape Hatch" Rule

### The Modern Definition

**useEffect is ONLY for synchronizing with external systems outside React's control.**

It is NOT for:
- ❌ Data fetching on component mount
- ❌ Deriving state from props or other state
- ❌ Mirroring props into state
- ❌ Business logic that could be pure functions
- ❌ Generic "on mount" initialization

It IS for:
- ✅ WebSocket subscriptions
- ✅ Browser APIs (localStorage, IntersectionObserver, ResizeObserver)
- ✅ DOM event listeners (window.resize, scroll)
- ✅ Third-party widgets (maps, chat, analytics)
- ✅ Timers tied to React state (setTimeout, setInterval)

### Anti-Pattern #1: Deriving State

**❌ Bad - Effect for derived state:**

```tsx
const [firstName, setFirstName] = useState("John")
const [lastName, setLastName] = useState("Doe")
const [fullName, setFullName] = useState("")

useEffect(() => {
  setFullName(`${firstName} ${lastName}`)
}, [firstName, lastName])
```

**✅ Good - Calculate during render:**

```tsx
const [firstName, setFirstName] = useState("John")
const [lastName, setLastName] = useState("Doe")

const fullName = `${firstName} ${lastName}` // Just compute it!
```

**Why**: Eliminates an entire render cycle and guarantees consistency.

### Anti-Pattern #2: Event Logic in Effects

**❌ Bad - Using effect for user actions:**

```tsx
const [submitted, setSubmitted] = useState(false)

useEffect(() => {
  if (submitted) {
    submitForm(formData)
  }
}, [submitted, formData])

// Later:
<button onClick={() => setSubmitted(true)}>Submit</button>
```

**✅ Good - Direct event handler:**

```tsx
const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault()
  await submitForm(formData)
}

// Later:
<form onSubmit={handleSubmit}>
  <button type="submit">Submit</button>
</form>
```

**Why**: User actions should trigger in event handlers, not via state flags.

### Anti-Pattern #3: Data Fetching on Mount

**❌ Bad - Client-side fetch in effect:**

```tsx
"use client"

function ProductsPage() {
  const [products, setProducts] = useState<Product[]>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/api/products')
      .then(res => res.json())
      .then(data => {
        setProducts(data)
        setLoading(false)
      })
  }, [])

  if (loading) return <Spinner />
  return <ProductsList products={products} />
}
```

**✅ Good - Server Component with async/await:**

```tsx
// app/products/page.tsx - Server Component (no "use client")
import { db } from '@/lib/db'

export default async function ProductsPage() {
  // This runs on the server, directly queries DB
  const products = await db.select().from(productsTable)

  return <ProductsList products={products} />
}
```

**Why**:
- Eliminates loading states
- Runs closer to data source (low latency)
- No client bundle size increase
- Content is server-rendered (better SEO)

### Legitimate useEffect Patterns

**✅ Good - External system subscription:**

```tsx
"use client"

function ChatRoom({ roomId }: { roomId: string }) {
  useEffect(() => {
    const connection = createConnection(roomId)
    connection.connect()

    return () => {
      connection.disconnect() // Cleanup
    }
  }, [roomId])

  return <div>Connected to {roomId}</div>
}
```

**✅ Good - Browser API sync:**

```tsx
"use client"

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true)

  useEffect(() => {
    const handleOnline = () => setIsOnline(true)
    const handleOffline = () => setIsOnline(false)

    window.addEventListener('online', handleOnline)
    window.addEventListener('offline', handleOffline)

    return () => {
      window.removeEventListener('online', handleOnline)
      window.removeEventListener('offline', handleOffline)
    }
  }, [])

  return isOnline
}
```

### Custom Hooks for Repeated Effects

When the same effect pattern appears multiple times:

**✅ Extract to custom hook:**

```tsx
// hooks/useMediaQuery.ts
"use client"

export function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false)

  useEffect(() => {
    const media = window.matchMedia(query)
    setMatches(media.matches)

    const listener = (e: MediaQueryListEvent) => setMatches(e.matches)
    media.addEventListener('change', listener)

    return () => media.removeEventListener('change', listener)
  }, [query])

  return matches
}

// Usage:
const isMobile = useMediaQuery('(max-width: 768px)')
```

---

## Checklist 2: Server Components and Data Fetching

### Server Components by Default

In Next.js App Router (`app/` directory):

**✅ Default: Server Component (no directive)**

```tsx
// app/dashboard/page.tsx
import { db } from '@/lib/db'

export default async function DashboardPage() {
  // Direct database access - runs on server
  const stats = await db.query.stats.findFirst()

  return <div>Revenue: ${stats.revenue}</div>
}
```

**✅ Opt-in: Client Component (with directive)**

```tsx
"use client" // Only add when you need interactivity

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)

  return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```

### Rules for Server vs Client Components

| Feature | Server Component | Client Component |
|---------|------------------|------------------|
| **Directive** | None (default) | `"use client"` |
| **Data fetching** | `async/await` DB queries | `use()` or data libraries |
| **Interactivity** | ❌ No event handlers | ✅ onClick, onChange, etc. |
| **React hooks** | ❌ No useState, useEffect | ✅ All hooks available |
| **Bundle size** | ✅ Zero JS shipped | ❌ Increases bundle |
| **Browser APIs** | ❌ No window, localStorage | ✅ Full access |
| **Environment vars** | ✅ Private vars safe | ⚠️ Only NEXT_PUBLIC_* |

### Request Memoization: End of Prop Drilling

**Problem**: Multiple components need the same data. Legacy solution: prop drilling or Context.

**Solution**: Request memoization with `React.cache`.

**✅ Modern pattern - No prop drilling:**

```tsx
// lib/data.ts
import { cache } from 'react'
import { db } from './db'

// Wrap in cache() for a
Files: 1
Size: 29.2 KB
Complexity: 39/100
Category: Web Dev

Related in Web Dev