Skip to content

Data fetching and cache (practical, not mystical) ​

Next has opinions about caching. They've also changed over time. Treat this as a mental model - verify against the docs for your Next version when you ship for real.

Fetch on the server (happy path) ​

tsx
async function getUser(id: string) {
  const res = await fetch(`https://api.example.com/users/${id}`, {
    next: { revalidate: 300 }, // seconds
  })
  if (!res.ok) throw new Error('Failed to load user')
  return res.json()
}

export default async function UserPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  const user = await getUser(id)
  return <h1>{user.name}</h1>
}

Comments for the code above:

  • In recent Next versions, params may be a Promise - await it
  • revalidate: 300 ≈ ISR-ish: serve cached, refresh in the background after 5 minutes
  • Errors can bubble to error.tsx

Dynamic always (no static cache) ​

tsx
fetch(url, { cache: 'no-store' })

Use when the data must be fresh every request (dashboards, per-user private data). Cost: less caching benefit.

Route Handlers (API routes) ​

ts
// app/api/hello/route.ts
import { NextResponse } from 'next/server'

export async function GET() {
  return NextResponse.json({ ok: true })
}

≈ Nuxt server routes / Nitro handlers. Good for webhooks, BFF endpoints, form posts.

Mutations from the client ​

Two common paths:

  1. Server Actions - functions you can call from forms / client, run on server
  2. Call your Route Handler / external API from a Client Component (TanStack Query still useful here)

Sketch of a Server Action:

tsx
// app/actions.ts
'use server'

export async function createPost(formData: FormData) {
  const title = String(formData.get('title') ?? '')
  // write to DB…
}
tsx
// in a Server Component page
<form action={createPost}>
  <input name="title" />
  <button type="submit">Create</button>
</form>

As you can see - progressive enhancement vibes. For rich client UIs, you'll still want Query or local state around the action call.

What I'd put where ​

NeedApproach
Public blog listingRSC + fetch + revalidate
User settings editorClient island + mutation (action or API)
Infinite scroll feedClient + TanStack Query
WebhookRoute Handler

Honest warning ​

Caching semantics have been a moving target across Next majors. When something "mysteriously" stays stale - check cache, revalidate, and whether you're in a static or dynamic render path. I've lost evenings to that. 😅

Next: routing UI - links, loading, errors.

Enjoy!