Next.js Data Fetching Patterns
Fetch server-side data efficiently without waterfalls using async Server Components
When to Use
- Fetching data required to render a page in a Server Component
- Optimizing multiple data dependencies to run in parallel instead of sequentially
- Deciding whether to fetch at the layout, page, or component level
- Passing fetched data to Client Components as props
- Replacing
getServerSidePropsandgetStaticPropsfrom the Pages Router
Instructions
- Fetch data directly in Server Components using
async/await— nouseEffector API abstraction layer needed. - Initiate all independent data fetches before any
awaitto enable parallel execution. - Use
Promise.all()when all fetches must resolve before rendering — one failure rejects all. - Use individual
awaits with Suspense boundaries to stream each fetch result independently as it resolves. - Fetch at the lowest component level that needs the data — avoid prop-drilling fetched data through multiple layers.
- Use
React.cache()to memoize a fetch function — safe to call in multiple components that need the same data within a request. - Avoid fetching in layouts when the data is only needed by a specific page — layouts block the segment render until resolved.
- Use
server-onlyimport to mark data access functions and prevent accidental client bundle inclusion.
// lib/data.ts — cached data access functions
import { cache } from 'react';
import 'server-only';
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
export const getPosts = cache(async (userId: string) => {
return db.post.findMany({ where: { authorId: userId } });
});
// app/users/[id]/page.tsx — parallel fetching
export default async function UserPage({ params }: { params: { id: string } }) {
// Initiate in parallel — do NOT await immediately
const userPromise = getUser(params.id);
const postsPromise = getPosts(params.id);
// Await both — total time = max(userTime, postsTime), not sum
const [user, posts] = await Promise.all([userPromise, postsPromise]);
if (!user) notFound();
return <UserProfile user={user} posts={posts} />;
}
// app/users/[id]/page.tsx — streaming with Suspense (alternative)
export default function UserPage({ params }: { params: { id: string } }) {
return (
<Suspense fallback={<UserSkeleton />}>
<UserContent userId={params.id} />
</Suspense>
);
}
async function UserContent({ userId }: { userId: string }) {
const user = await getUser(userId); // suspends this boundary only
return <UserProfile user={user} />;
}
Details
In the App Router, data fetching is co-located with the components that need it — no centralized data loading layer required. Server Components can await database queries, fetch calls, and SDK methods directly in the component body.
Sequential vs parallel: Two await statements in sequence create a waterfall — each waits for the previous. Initiating both promises before awaiting either enables parallelism. With Promise.all, both start simultaneously and the total time equals the slowest, not the sum.
React.cache() for deduplication: Multiple Server Components requesting the same data within one render pass will each trigger a separate fetch unless the fetch function is wrapped with React.cache(). This is different from the Next.js Data Cache — React.cache() deduplicates within a single request only.
Fetch vs ORM: fetch() in Server Components integrates with the Next.js Data Cache (caching, revalidation, tags). ORM calls (Prisma, Drizzle) do not — use unstable_cache from next/cache to cache them.
Layout vs page fetching: Fetching in layout.tsx blocks the layout render, which blocks all child pages. Only fetch in layouts what every child route genuinely needs (e.g., current user session). Fetch page-specific data in page.tsx or in dedicated async components.
Request deduplication: Next.js automatically deduplicates identical fetch() calls (same URL, same options) within a request lifecycle using Request Memoization. Multiple components fetching the same URL only trigger one network request.
Source
https://nextjs.org/docs/app/building-your-application/data-fetching/fetching
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.