Data Fetching Patterns in Next.js
The Restaurant Kitchen Analogy 🍳
Imagine you’re running a busy restaurant. Your kitchen has different ways to prepare orders:
- Sometimes the chef preps everything before the waiter arrives
- Sometimes the waiter asks the kitchen mid-service
- Sometimes multiple dishes cook at once
- Sometimes dishes must be made one after another
Next.js data fetching works exactly like this kitchen!
1. Server vs Client Fetching
What’s the Difference?
Think of two different workers at a restaurant:
Server Fetching = The Kitchen (Back of House)
- Work happens before food leaves the kitchen
- Customer never sees the preparation
- Faster for the customer—food arrives ready!
Client Fetching = The Waiter (Front of House)
- Work happens at the table
- Customer watches and waits
- More interactive—can change orders live!
Server Fetching Example
The kitchen prepares everything before serving:
// This runs on the SERVER
// Customer never sees this code run!
async function MenuPage() {
const dishes = await fetch(
'https://api.restaurant.com/menu'
);
const menu = await dishes.json();
return (
<ul>
{menu.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
Why use Server Fetching?
- Page loads faster (data already there!)
- Better for search engines (Google sees the content)
- Secrets stay hidden (API keys never reach browser)
Client Fetching Example
The waiter brings ingredients to the table:
'use client'
import { useState, useEffect } from 'react';
function LiveOrders() {
const [orders, setOrders] = useState([]);
useEffect(() => {
// This runs in the BROWSER
fetch('/api/orders')
.then(res => res.json())
.then(data => setOrders(data));
}, []);
return <OrderList orders={orders} />;
}
Why use Client Fetching?
- Needs user interaction (button clicks, typing)
- Data changes constantly (live updates)
- User-specific data (after page loads)
graph TD A["User Visits Page"] --> B{What kind of data?} B -->|Static/SEO needed| C["Server Fetch"] B -->|Interactive/Live| D["Client Fetch"] C --> E["Fast First Load!"] D --> F["Updates in Real-Time!"]
2. Parallel Data Fetching
The Problem
Imagine ordering pizza AND salad. What if the kitchen made the pizza first, THEN started the salad? You’d wait forever!
The Solution: Cook Everything at Once!
async function DinnerPage() {
// BAD: Sequential (slow!)
// const pizza = await getPizza();
// const salad = await getSalad();
// Total time: pizza + salad
// GOOD: Parallel (fast!)
const [pizza, salad] = await Promise.all([
getPizza(),
getSalad()
]);
// Total time: whichever takes longer
return (
<div>
<PizzaCard pizza={pizza} />
<SaladCard salad={salad} />
</div>
);
}
Visual Comparison
graph TD subgraph Sequential ["Sequential: SLOW"] A1["Start"] --> A2["Fetch Pizza: 2s"] A2 --> A3["Fetch Salad: 1s"] A3 --> A4["Done: 3 seconds!"] end subgraph Parallel ["Parallel: FAST"] B1["Start"] --> B2["Fetch Pizza: 2s"] B1 --> B3["Fetch Salad: 1s"] B2 --> B4["Done: 2 seconds!"] B3 --> B4 end
Golden Rule: If two things don’t depend on each other, fetch them together!
3. Sequential Data Fetching
When Order Matters
Sometimes you MUST wait. Like at a restaurant:
- First, check if table is available
- THEN take the order
- THEN cook the food
You can’t cook before knowing what they want!
Real Example: User Profile Then Posts
async function ProfilePage({ userId }) {
// Step 1: Get the user first
const user = await getUser(userId);
// Step 2: Now we can get THEIR posts
const posts = await getPosts(user.id);
return (
<div>
<h1>{user.name}</h1>
<PostList posts={posts} />
</div>
);
}
Why Sequential Sometimes?
graph TD A["Get User"] --> B{User Found?} B -->|Yes| C[Get User's Posts] B -->|No| D["Show Error"] C --> E["Display Everything"]
Use Sequential when:
- Step 2 needs data from Step 1
- You need to check permissions first
- Data depends on previous results
4. Colocated Data Fetching
The Smart Kitchen Layout
In a great restaurant kitchen, ingredients are placed right where they’re needed:
- Pasta station has pasta, sauce, cheese nearby
- Grill station has meats, seasonings, tools nearby
No running around the kitchen!
What is Colocation?
Put the data fetching RIGHT NEXT TO the component that uses it!
OLD Way (Data passed down):
// Page fetches EVERYTHING
async function Page() {
const user = await getUser();
const posts = await getPosts();
const comments = await getComments();
return <Profile
user={user}
posts={posts}
comments={comments}
/>;
}
NEW Way (Colocated):
// Each component fetches its OWN data!
async function Page() {
return (
<div>
<UserCard /> {/* Fetches user */}
<PostList /> {/* Fetches posts */}
<CommentFeed /> {/* Fetches comments */}
</div>
);
}
async function UserCard() {
const user = await getUser();
return <div>{user.name}</div>;
}
Why This is Magical ✨
graph TD A["Page Component"] --> B["UserCard"] A --> C["PostList"] A --> D["CommentFeed"] B --> B1["Fetches own user data"] C --> C1["Fetches own posts data"] D --> D1["Fetches own comments"] style B1 fill:#90EE90 style C1 fill:#90EE90 style D1 fill:#90EE90
Benefits:
- Each component is independent
- Easy to move components around
- Code is easier to understand
- Next.js automatically runs them in parallel!
5. Request Deduplication
The Waiter’s Nightmare
Imagine 5 tables all order the same dish at once. A bad waiter runs to the kitchen 5 times. A smart waiter says:
“Hey chef, I need 5 of the special!”
One trip. Five dishes.
Next.js Does This Automatically!
async function Header() {
const user = await getUser(); // Request 1
return <div>Welcome, {user.name}</div>;
}
async function Sidebar() {
const user = await getUser(); // Same request!
return <div>Profile: {user.email}</div>;
}
async function Page() {
return (
<div>
<Header />
<Sidebar />
</div>
);
}
Even though we call getUser() twice, Next.js is smart:
graph TD A["Header calls getUser"] --> C{Already fetching?} B["Sidebar calls getUser"] --> C C -->|First time| D["Make ONE request"] C -->|Already in progress| E["Wait for same request"] D --> F["Share result with both!"] E --> F
How It Works
Next.js uses the built-in fetch function and:
- Remembers identical requests
- Makes the call only ONCE
- Shares the result with everyone who asked
You get this for FREE when using fetch!
// These are IDENTICAL requests
// Next.js will only call the API once!
const data1 = await fetch('https://api.example.com/user');
const data2 = await fetch('https://api.example.com/user');
Quick Summary
| Pattern | When to Use | Kitchen Analogy |
|---|---|---|
| Server Fetch | SEO, secrets, fast load | Chef preps in kitchen |
| Client Fetch | Interactive, live data | Waiter at table |
| Parallel | Independent data | Cook dishes at once |
| Sequential | Data depends on data | Check table, then order |
| Colocated | Self-contained parts | Ingredients at station |
| Deduplication | Same data many places | One order, many plates |
You’re Now a Data Fetching Chef! 👨‍🍳
Remember:
- Server first = Faster pages
- Parallel when possible = Shorter waits
- Sequential when needed = Correct order
- Colocate data = Cleaner code
- Trust deduplication = No wasted requests
Your Next.js kitchen is ready to serve amazing web experiences!
