Fetching in Server Components
The idea in one sentence
Section titled “The idea in one sentence”A Server Component is an async function, so it can await a fetch or a database call directly in the component body — there is no useEffect, no loading state, and no client-to-API round trip.
Fetch directly in an async component
Section titled “Fetch directly in an async component”In the App Router, the component runs on the server. You fetch where you render, and the data never touches the client bundle until the HTML is sent.
export default async function ProductsPage() { // Runs on the server — the API key and response stay server-side const res = await fetch('https://api.example.com/products') const products = await res.json() return <ProductList products={products} />}Because the code runs on the server, you can also talk to your database or ORM directly — no API route in between.
import { db } from '@/lib/db'
export default async function UsersPage() { // Query the database straight from the Server Component const users = await db.user.findMany() return <UserList users={users} />}Sequential vs. parallel: avoid waterfalls
Section titled “Sequential vs. parallel: avoid waterfalls”If you await one request and then await the next, the second cannot start until the first resolves — a waterfall. When requests do not depend on each other, start them together with Promise.all.
async function getUser(id: string) { const res = await fetch(`https://api.example.com/users/${id}`) return res.json()}
async function getPosts(id: string) { const res = await fetch(`https://api.example.com/users/${id}/posts`) return res.json()}
export default async function Dashboard() { // Parallel: both requests fire at once, no waterfall const [user, posts] = await Promise.all([getUser('1'), getPosts('1')]) return <Profile user={user} posts={posts} />}Dedupe with Request Memoization and React.cache()
Section titled “Dedupe with Request Memoization and React.cache()”Within a single render pass, Next.js memoizes identical fetch calls automatically — call the same URL in three components and the network sees one request. For non-fetch work like a database query, wrap it in React’s cache() to get the same per-request deduplication.
import { cache } from 'react'import { db } from '@/lib/db'
// Called by many components in one render — the query runs once per requestexport const getUser = cache(async (id: string) => { return db.user.findUnique({ where: { id } })})Where NOT to fetch
Section titled “Where NOT to fetch”A Client Component cannot await in its body and must not touch the database directly. It reaches data through a Route Handler (or a data library), which runs on the server on its behalf.
'use client'import { useState } from 'react'
export function SearchBox() { const [results, setResults] = useState([]) async function onSearch(q: string) { // Client components fetch through a Route Handler, never the DB const res = await fetch(`/api/search?q=${q}`) setResults(await res.json()) } return <input onInput={(e) => onSearch(e.currentTarget.value)} />}flowchart LR A["Request"] --> B["await getUser()"] B --> C["await getPosts()"] C --> D["Render (late)"] A --> E["Promise.all"] E --> F["getUser() + getPosts() together"] F --> G["Render (sooner)"]