Next.js Data Fetching Patterns
Master the different data fetching strategies in Next.js, from server-side rendering to static generation and incremental static regeneration.
FrontendIntermediate18 min read
AI Summary
Quick context for Next.js Data Fetching Patterns
Next.js Data Fetching Patterns is a intermediate guide in Frontend. Master the different data fetching strategies in Next.js, from server-side rendering to static generation and incremental static regeneration.. Key topics: data-fetching, nextjs, react, ssr.
## Choosing the Right Data Fetching Strategy
Next.js offers multiple data fetching approaches, each suited for different use cases. Understanding when to use each pattern is critical for building performant applications that scale.
The three primary strategies are Server-Side Rendering (SSR), Static Site Generation (SSG), and Client-Side Fetching. Each has trade-offs in terms of performance, SEO, and developer experience.
## Server-Side Rendering (SSR)
SSR generates HTML on every request. This is ideal for pages where content changes frequently or depends on user-specific data.
```typescript
// app/dashboard/page.tsx
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
async function getDashboardData(userId: string) {
const res = await fetch(`https://api.example.com/dashboard/${userId}`, {
cache: "no-store",
});
if (!res.ok) throw new Error("Failed to fetch dashboard data");
return res.json();
}
export default async function DashboardPage() {
const session = await getServerSession();
if (!session) redirect("/login");
const data = await getDashboardData(session.user.id);
return (
);
}
```
The `cache: "no-store"` option ensures fresh data on every request. Without it, Next.js may return cached responses from previous fetches.
## Static Site Generation (SSG)
SSG generates HTML at build time. Use this for pages where content rarely changes and can be cached effectively.
```typescript
// app/blog/[slug]/page.tsx
import { notFound } from "next/navigation";
interface PageProps {
params: { slug: string };
}
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600 },
});
if (!res.ok) return null;
return res.json();
}
export async function generateStaticParams() {
const posts = await fetch("https://api.example.com/posts").then((r) =>
r.json()
);
return posts.map((post) => ({ slug: post.slug }));
}
export default async function BlogPost({ params }: PageProps) {
const post = await getPost(params.slug);
if (!post) notFound();
return (
);
}
```
## Incremental Static Regeneration (ISR)
ISR lets you update static pages after build time without a full rebuild. The `revalidate` option specifies how many seconds before the page is regenerated.
```typescript
// app/products/page.tsx
async function getProducts() {
const res = await fetch("https://api.example.com/products", {
next: { revalidate: 60 }, // Regenerate every 60 seconds
});
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
);
}
```
## Parallel Data Fetching
Fetch independent data sources simultaneously to reduce waterfall requests and improve load times.
```typescript
// app/analytics/page.tsx
async function getAnalytics() {
const res = await fetch("https://api.example.com/analytics");
return res.json();
}
async function getRecentActivity() {
const res = await fetch("https://api.example.com/activity");
return res.json();
}
export default async function AnalyticsPage() {
const [analytics, activity] = await Promise.all([
getAnalytics(),
getRecentActivity(),
]);
return (
);
}
```
## Common Pitfalls
- **Over-fetching**: Request only the fields you need, especially from large APIs
- **Missing error handling**: Always check `res.ok` and handle failure cases
- **Ignoring revalidation**: Stale data can confuse users; set appropriate revalidation windows
- **Using client-side fetching for SEO**: Search engines cannot execute JavaScript, so critical content should be server-rendered
- **Forgetting `use client`**: Client components must include the directive at the top of the file
## Decision Framework
| Scenario | Strategy |
|---|---|
| Public marketing pages | SSG |
| User-specific dashboards | SSR |
| Product catalogs with prices | ISR |
| Real-time notifications | Client-Side |
| Blog posts updated daily | ISR with short revalidation |
Welcome, {session.user.name}
{post.title}
{products.map((product) => (
))}
);
}
```
## Client-Side Fetching
For highly interactive data that changes based on user actions, client-side fetching with React hooks is the right choice.
```typescript
"use client";
import { useState, useEffect } from "react";
function useNotifications() {
const [notifications, setNotifications] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchNotifications() {
const res = await fetch("/api/notifications");
const data = await res.json();
setNotifications(data);
setLoading(false);
}
fetchNotifications();
}, []);
return { notifications, loading };
}
export function NotificationBell() {
const { notifications, loading } = useNotifications();
return (
Continue with related content
Suggested next reads and tools from the knowledge graph.