C.W.K.
Stream
Lesson 05 of 08 · published

Data in Server Components

~20 min · fetch, async, Promise.all

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

The simplest data path

Server Components are async functions. You await data and render. There's no useEffect, no loading state machine, no client cache to invalidate.

Two ways to read data

  1. fetch() — for HTTP/JSON sources. Caching is controlled per call (we'll cover that in the data-fetching track).
  2. Direct DB / SDK calls — Prisma, Drizzle, Postgres clients, internal services.

Parallel by default; waterfall on purpose

Independent fetches should be in Promise.all. The framework will not magically parallelize your sequential awaits — that's a JavaScript reality, not a framework one. Sequential is fine when the second fetch genuinely depends on the first.

Where to fetch

Fetch in the component that uses the data. Don't drill props from the page through three layers; have the leaf component fetch what it needs. The framework deduplicates identical fetch() calls during a single render pass, so two siblings asking for the same URL trigger one network request.

Code

Direct fetch in a Server Component·tsx
export default async function Posts() {
  const res = await fetch('https://api.example.com/posts', {
    next: { revalidate: 60 }, // ISR: 60s
  });
  const posts = await res.json();
  return <PostList posts={posts} />;
}
Direct DB read·tsx
import { db } from '@/lib/db';

export default async function Users() {
  const users = await db.user.findMany({
    include: { posts: true },
    orderBy: { createdAt: 'desc' },
  });
  return <UserTable users={users} />;
}
Parallel fetches·tsx
export default async function Dashboard() {
  const [user, stats, alerts] = await Promise.all([
    getUser(),
    getStats(),
    getAlerts(),
  ]);
  return (
    <>
      <UserHeader user={user} />
      <StatsGrid stats={stats} />
      <AlertBanner alerts={alerts} />
    </>
  );
}
Fetch in the leaf, not the page·tsx
// app/page.tsx
export default function Page() {
  return (
    <>
      <Header />
      <Sidebar />
      <Feed />
    </>
  );
}

// Each leaf fetches its own data — Next dedupes identical URLs.
async function Header()  { const user = await getMe();         return <Nav user={user} />; }
async function Sidebar() { const cats = await getCategories(); return <Cats list={cats} />; }
async function Feed()    { const posts = await getFeed();      return <Posts items={posts} />; }

External links

Exercise

Refactor a page that drills data as a prop through three layers. Move the fetch into the leaf that actually uses it. Confirm in DevTools that rendering still works and the page's network panel still shows one request.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.