Data Fetching Patterns in the Next.js App Router
In the App Router you fetch data directly in async Server Components. No useEffect, no API route in between. The main thing to get right is avoiding slow request waterfalls.
Fetch where the data is needed
export default async function Dashboard() {
const stats = await getStats(); // runs on the server; can talk to the DB directly
return <StatsGrid stats={stats} />;
}
Avoid waterfalls: run independent requests in parallel
// Slow: the second request waits for the first
const user = await getUser(id);
const orders = await getOrders(id);
// Fast: both start at once
const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);
Stream slow parts with Suspense
Don't let one slow query block the whole page. Move it into its own component and wrap it in <Suspense>:
import { Suspense } from 'react';
export default async function ProductPage({ params }) {
const { id } = await params;
const product = await getProduct(id); // fast, needed for the main content
return (
<>
<ProductDetails product={product} />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={id} /> {/* slow; streams in when ready */}
</Suspense>
</>
);
}
A loading.tsx file next to page.tsx does the same for the whole route segment.
Deduplicate repeated calls
If a layout and a page both need the current user, wrap the loader with React's cache so it runs once per request:
import { cache } from 'react';
export const getCurrentUser = cache(async () => {
const session = await auth();
return session ? db.user.findUnique({ where: { id: session.userId } }) : null;
});
When to fetch on the client
- Data that changes with user interaction (live search, infinite scroll).
- Real-time updates (polling, WebSockets).
- Use a library like TanStack Query or SWR there.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.