await fetch — That's the Whole API
Data Fetching in Server Components
Server Components can `await` directly. There is no SWR or React Query at the server boundary — just async functions and `fetch`.
What you'll learn
- Await `fetch` in an async Server Component
- Compose multiple awaits cleanly
- Use `Promise.all` for parallel requests
Because Server Components run on the server, they can be async functions. The data
fetching API is just await plus whatever client you like — fetch, a database driver,
an ORM, anything Node.js can run.
Awaiting fetch Directly
type Post = { id: string; title: string; body: string }
export default async function PostPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const post: Post = await fetch(`https://api.example.com/posts/${id}`).then(r => r.json())
return <article><h1>{post.title}</h1><p>{post.body}</p></article>
} No useEffect, no loading state in the component. If the fetch throws, Next routes the
error to the nearest error.tsx.
Parallel Requests
Awaiting one after another serializes them. Use Promise.all to fire requests in parallel.
export default async function Dashboard() {
const [user, posts] = await Promise.all([
fetch('/api/me').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
])
return <Layout user={user} posts={posts} />
} Total latency is now the slower of the two, not the sum.
Direct Database Access
You do not have to go through HTTP. A Server Component can talk to a database client directly because it runs server-side.
import { db } from '@/lib/db'
export default async function Posts() {
const posts = await db.posts.findMany({ orderBy: { createdAt: 'desc' } })
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}