Fetching data in Server Components

Next.js · Essentials · 13 min

Server-side data fetching is the default and most efficient way to load data in an App Router app.

In the App Router you fetch data where you need it: make a Server Component async and await the data, from fetch, an ORM or a database client.

export default async function Page() {
  const posts = await db.post.findMany()
  return <PostList posts={posts} />
}

No useEffect, no loading flags, no API route in between, and secrets stay on the server.

Things to know:

  • fetch results are not cached by default; uncached data is fetched at request time. Caching is opt-in with 'use cache' (covered later).
  • Identical fetch calls in one render are memoised, so several components can request the same URL without duplicate requests. For database calls, wrap the function with React's cache() to get the same per-request deduplication.
  • Sequential awaits create waterfalls. Start independent requests together with Promise.all, or let separate components fetch their own data inside separate Suspense boundaries so they stream in parallel.
  • Handle failures: check res.ok, and decide between rendering a message, notFound() or throwing to an error boundary.

Client-side fetching is still useful for data that changes after load or depends on client state; use a library like SWR or TanStack Query, or pass a promise from a Server Component and read it with use().

Example

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

export const getUser = cache(async (id: string) => db.user.find(id))  // deduped per request

// app/users/[id]/page.tsx
import { getUser } from '@/lib/data'

async function getPosts(userId: string) {
  const res = await fetch(`https://api.example.com/users/${userId}/posts`)
  if (!res.ok) throw new Error('Failed to load posts')
  return (await res.json()) as { id: number; title: string }[]
}
export default async function UserPage({ params }: PageProps<'/users/[id]'>) {
  const { id } = await params
  // start both requests at once instead of awaiting one after the other
  const [user, posts] = await Promise.all([getUser(id), getPosts(id)])
  return (
    <section>
      <h1>{user.name}</h1>
      <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
    </section>
  )
}

Remember

  • Make Server Components async and await data directly
  • fetch is uncached by default; identical fetches in a render are memoised
  • Use Promise.all or separate Suspense boundaries to avoid waterfalls; cache() dedupes DB calls

Watch out for

  • Awaiting independent requests one after another, so the page takes the sum of their durations.

Check yourself

  1. Two independent requests take 300 ms and 500 ms. They are awaited sequentially in a Server Component. Roughly how long until the page can render?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nextjs.org/docs/app/getting-started/fetching-data