In the App Router, Server Components are async functions that fetch data directly using fetch() or database queries — parallel fetching with Promise.all avoids waterfalls, request deduplication prevents redundant calls, and client-side fetching uses SWR or React Query for interactive data needs.
Server Components are async functions that fetch data directly using fetch(), database queries, or file reads — no special API needed.
Use Promise.all to fetch data in parallel and avoid waterfall requests where each fetch waits for the previous one.
React deduplicates identical fetch() calls during a single server render — multiple components can fetch the same URL without redundant requests.
Client Components use SWR or React Query for real-time data, user interactions, and data that changes post-load — they cannot be async.
Data fetching in Next.js has fundamentally changed with the App Router. Instead of special functions (getStaticProps, getServerSideProps), Server Components fetch data directly as async functions.
Server Components can be async and call fetch(), query databases, or read files directly:
async function UserProfile({ userId }: { userId: string }) {
const user = await fetch(`https://api.example.com/users/${userId}`);
const data = await user.json();
return <h1>{data.name}</h1>;
}No special API is needed — it's just an async function that returns JSX. This is simpler than the Pages Router's getStaticProps/getServerSideProps because data fetching lives inside the component that needs the data.
Sequential fetching creates waterfalls — each request waits for the previous one:
// Sequential (waterfall) — bad
const user = await getUser(id);
const posts = await getPosts(id); // waits for getUser to finishUse Promise.all for parallel fetching:
// Parallel — good
const [user, posts] = await Promise.all([getUser(id), getPosts(id)]);Alternatively, start fetches at the page level and pass promises to child components, which await them individually. This pattern combined with Suspense enables streaming — each component resolves independently.
React automatically deduplicates identical fetch() calls during a single server render. If UserProfile and UserStats both call fetch('/api/users/1'), only one network request is made. This means you can fetch data where you need it without worrying about redundancy.
Deduplication only works with fetch() using the same URL and options. For non-fetch calls (direct database queries), use React's cache() function to deduplicate.
// Uncached — fresh on every request (Next.js 15+ default)
fetch(url);
// Cached indefinitely
fetch(url, { cache: 'force-cache' });
// Revalidate after 1 hour
fetch(url, { next: { revalidate: 3600 } });
// Tag for on-demand revalidation
fetch(url, { next: { tags: ['users'] } });For databases, ORMs, or other non-fetch data sources, use unstable_cache (or the 'use cache' directive in Next.js 15+) to add caching:
import { unstable_cache } from 'next/cache';
const getCachedUser = unstable_cache(
async (id: string) => db.users.findById(id),
['user'],
{ revalidate: 3600, tags: ['users'] }
);Client Components cannot be async — they need client-side data fetching libraries:
Use client-side fetching for: real-time data, user-specific data after initial load, infinite scrolling, and data that changes based on user interaction.
The Pages Router used special exported functions:
getStaticProps — Fetch data at build time (SSG)getServerSideProps — Fetch data per request (SSR)getStaticPaths — Define dynamic routes for static generationThese still work in the pages/ directory but are not available in the App Router.
Server Components fetch data directly as async functions — no special APIs needed. Use Promise.all for parallel fetching to avoid waterfalls. React deduplicates identical fetch calls automatically. Client Components use SWR or React Query for interactive data needs. The Pages Router's getStaticProps/getServerSideProps are replaced by the simpler async component pattern.
Fun Fact
The async Server Component pattern was controversial when announced because React had always been synchronous. The React team had to rethink how components work at a fundamental level — a component that awaits a database query was an entirely new paradigm. This simplification eliminated the need for getStaticProps/getServerSideProps, which were the most confusing parts of the Pages Router for newcomers.