react-next-modern
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.
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 aRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.