# Data Fetching

> Data fetching patterns for Next.js App Router — Server Components, Server Actions, React Query, and streaming. Use when implementing or reviewing data fetching logic, choosing between RSC and client-side fetching, or designing API routes.

- Skill: `lennney/data-fetching` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lennney/data-fetching`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lennney/data-fetching/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: lennney (https://skillmd.com/u/lennney)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lennney/data-fetching

---


# Data Fetching (Next.js)

Canonical patterns for fetching data in Next.js App Router.

## Fetch Strategy Decision

```
Need data for a page / route?
├─ Data is public / user-agnostic?
│   ├─ Static (rarely changes) → Server Component + fetch() with { cache: 'force-cache' }
│   ├─ Dynamic (per-request)   → Server Component + fetch() (default dynamic)
│   └─ Revalidating            → Server Component + fetch() with { next: { revalidate: N } }
├─ Data is user-specific?
│   └─ Server Component + cookies()/headers() (auto-deduped, secure)
├─ Need real-time / polling?
│   └─ Client Component + React Query / SWR
└─ Need user-initiated mutation?
    └─ Server Action (form action / event handler)
```

## Patterns

### 1. Server Component Data Fetching (Preferred)

```tsx
// app/posts/page.tsx — Server Component, fetch runs on server, zero client JS
export default async function PostsPage() {
  const posts = await fetch('https://api.example.com/posts', {
    next: { revalidate: 60 }  // ISR: revalidate every 60s
  }).then(r => r.json())

  return <PostList posts={posts} />
}
```

### 2. Parallel Data Fetching

```tsx
export default async function Dashboard() {
  // Parallel — both fetches start at the same time
  const [user, analytics] = await Promise.all([
    getUser(),
    getAnalytics()
  ])

  return <DashboardView user={user} analytics={analytics} />
}
```

### 3. Streaming with Suspense

```tsx
import { Suspense } from 'react'

export default function Page() {
  return (
    <div>
      <Header />              {/* Shown instantly */}
      <Suspense fallback={<Skeleton />}>
        <SlowDataComponent />  {/* Streamed in when ready */}
      </Suspense>
    </div>
  )
}
```

### 4. Server Actions (Mutations)

```tsx
'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title')
  await db.post.create({ title })
  revalidatePath('/posts')  // Revalidate the cache
}
```

### 5. Client-Side React Query (Real-time / Polling)

When Server Components aren't enough (polling, real-time, window-focus refetch):
- Use React Query / TanStack Query
- Keep the fetch logic in a custom hook
- Use `initialData` from Server Components to hydrate

## Anti-Patterns

| Anti-Pattern | Why | Fix |
|-------------|-----|-----|
| Fetching in `useEffect` for initial page data | Wastes server capability, hurts UX | Use Server Component fetching |
| Sequential fetches in Server Components | Doubles load time | Use `Promise.all()` |
| Mixing server/client fetch for the same data | Redundant network calls | Fetch once in Server, pass down |
| Over-fetching in RSC | Large bundle from serialized data | Only pass what the client needs |
| `useEffect` + fetch for mutations | No built-in loading/error states | Use Server Actions + `useActionState` |

## Checklist

- [ ] Could this fetch happen in a Server Component? (prefer it)
- [ ] Are parallel fetches batched with `Promise.all`?
- [ ] Is the revalidation strategy correct? (time-based / on-demand / no cache)
- [ ] Are loading states handled via `loading.tsx` or Suspense?
- [ ] Are error states handled via `error.tsx`?
- [ ] Are Server Actions used for mutations (not API routes)?
- [ ] Is sensitive data only fetched server-side, never exposed to client JS?
