- name
- nextjs-data-fetching
- user-invocable
- false
- description
- Use when next.js data fetching patterns including SSG, SSR, and ISR. Use when building data-driven Next.js applications.
- allowed-tools
- ["Bash","Read"]
# Next.js Data Fetching
Master data fetching in Next.js with static generation, server-side
rendering, and incremental static regeneration.
## Fetch with Caching Strategies
```typescript
// Default: 'force-cache' (similar to SSG)
export default async function Page() {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
return <div>{json.title}</div>;
}
// No caching: 'no-store' (similar to SSR)
export default async function DynamicPage() {
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
const json = await data.json();
return <div>{json.title}</div>;
}
// Revalidate every 60 seconds (ISR)
export default async function RevalidatedPage() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }
});
const json = await data.json();
return <div>{json.title}</div>;
}
// Per-route caching config
export const revalidate = 3600; // Revalidate every hour
export default async function Page() {
const data = await fetch('https://api.example.com/data');
return <div>{data.title}</div>;
}
// Dynamic rendering
export const dynamic = 'force-dynamic'; // Equivalent to cache: 'no-store'
export const dynamic = 'force-static'; // Equivalent to cache: 'force-cache'
export const dynamic = 'error'; // Error if dynamic functions used
export const dynamic = 'auto'; // Default behavior
```
## Static Generation with generateStaticParams
```typescript
// app/posts/[id]/page.tsx
interface Post {
id: string;
title: string;
content: string;
}
// Generate static paths at build time
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map((post: Post) => ({
id: post.id.toString()
}));
}
export default async function Post({ params }: { params: { id: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.id}`, {
next: { revalidate: 3600 } // Revalidate hourly
}).then(r => r.json());
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// Multiple dynamic segments
// app/shop/[category]/[product]/page.tsx
export async function generateStaticParams() {
const categories = await getCategories();
const paths = await Promise.all(
categories.map(async (category) => {
const products = await getProducts(category.slug);
return products.map((product) => ({
category: category.slug,
product: product.slug
}));
})
);
return paths.flat();
}
export default async function Product({
params
}: {
params: { category: string; product: string }
}) {
const product = await getProduct(params.category, params.product);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
</div>
);
}
// Limit static generation for large datasets
export const dynamicParams = true; // Default: generate on-demand
export const dynamicParams = false; // Return 404 for ungenerated paths
export async function generateStaticParams() {
// Only generate top 100 posts at build time
const posts = await getPosts({ limit: 100 });
return posts.map((post) => ({
id: post.id
}));
}
```
## Time-Based Revalidation (ISR)
```typescript
// Page-level revalidation
export const revalidate = 60; // Revalidate every 60 seconds
export default async function Page() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
</article>
))}
</div>
);
}
// Per-request revalidation
export default async function Page() {
const staticData = await fetch('https://api.example.com/static', {
next: { revalidate: 3600 } // Cache for 1 hour
}).then(r => r.json());
const dynamicData = await fetch('https://api.example.com/dynamic', {
next: { revalidate: 10 } // Cache for 10 seconds
}).then(r => r.json());
return (
<div>
<div>Static: {staticData.value}</div>
<div>Dynamic: {dynamicData.value}</div>
</div>
);
}
// Mixed caching strategies
export default async function Dashboard() {
// Never cache (always fresh)
const liveData = await fetch('https://api.example.com/live', {
cache: 'no-store'
}).then(r => r.json());
// Cache indefinitely (build-time only)
const staticData = await fetch('https://api.example.com/static', {
cache: 'force-cache'
}).then(r => r.json());
// Revalidate periodically
const periodicData = await fetch('https://api.example.com/periodic', {
next: { revalidate: 300 }
}).then(r => r.json());
return (
<div>
<LiveStats data={liveData} />
<StaticContent data={staticData} />
<PeriodicUpdates data={periodicData} />
</div>
);
}
```
## On-Demand Revalidation
```typescript
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const secret = request.nextUrl.searchParams.get('secret');
// Verify secret token
if (secret !== process.env.REVALIDATE_SECRET) {
return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
}
const path = request.nextUrl.searchParams.get('path');
if (path) {
// Revalidate specific path
revalidatePath(path);
return NextResponse.json({ revalidated: true, path });
}
return NextResponse.json({ error: 'Missing path' }, { status: 400 });
}
// app/posts/[slug]/page.tsx - Using cache tags
export default async function Post({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { tags: ['posts', `post-${params.slug}`] }
}).then(r => r.json());
return <article>{post.content}</article>;
}
// app/api/revalidate-tag/route.ts
export async function POST(request: NextRequest) {
const tag = request.nextUrl.searchParams.get('tag');
if (tag) {
revalidateTag(tag);
return NextResponse.json({ revalidated: true, tag });
}
return NextResponse.json({ error: 'Missing tag' }, { status: 400 });
}
// Server Action for revalidation
'use server';
import { revalidatePath } from 'next/cache';
export async function updatePost(id: string, data: FormData) {
await db.post.update({ where: { id }, data });
// Revalidate the post page
revalidatePath(`/posts/${id}`);
// Revalidate the posts list
revalidatePath('/posts');
}
```
## Parallel Data Fetching
```typescript
// Fetch multiple resources in parallel
export default async function Page() {
const [posts, categories, tags] = await Promise.all([
fetch('https://api.example.com/posts').then(r => r.json()),
fetch('https://api.example.com/categories').then(r => r.json()),
fetch('https://api.example.com/tags').then(r => r.json())
]);
return (
<div>
<PostList posts={posts} />
<CategoryList categories={categories} />
<TagCloud tags={tags} />
</div>
);
}
// Parallel fetching with different cache strategies
export default async function Dashboard() {
const [stats, recentActivity, settings] = await Promise.all([
fetch('https://api.example.com/stats', {
next: { revalidate: 60 }
}).then(r => r.json()),
fetch('https://api.example.com/activity', {
cache: 'no-store'
}).then(r => r.json()),
fetch('https://api.example.com/settings', {
cache: 'force-cache'
}).then(r => r.json())
]);
return (
<div>
<Stats data={stats} />
<Activity data={recentActivity} />
<Settings data={settings} />
</div>
);
}
// Fetching with fallbacks
export default async function Page() {
const results = await Promise.allSettled([
fetch('https://api.example.com/required').then(r => r.json()),
fetch('https://api.example.com/optional1').then(r => r.json()),
fetch('https://api.example.com/optional2').then(r => r.json())
]);
const [required, optional1, optional2] = results;
return (
<div>
{required.status === 'fulfilled' && <Required data={required.value} />}
{optional1.status === 'fulfilled' && <Optional1 data={optional1.value} />}
{optional2.status === 'fulfilled' && <Optional2 data={optional2.value} />}
</div>
);
}
```
## Streaming and Suspense
```typescript
// app/posts/page.tsx
import { Suspense } from 'react';
export default function PostsPage() {
return (
<div>
<h1>Blog Posts</h1>
{/* Stream featured posts */}
<Suspense fallback={<FeaturedSkeleton />}>
<FeaturedPosts />
</Suspense>
{/* Stream all posts */}
<Suspense fallback={<PostsSkeleton />}>
<AllPosts />
</Suspense>
{/* Stream comments */}
<Suspense fallback={<CommentsSkeleton />}>
<RecentComments />
</Suspense>
</div>
);
}
async function FeaturedPosts() {
const posts = await fetch('https://api.example.com/posts/featured', {
next: { revalidate: 300 }
}).then(r => r.json());
return (
<div className="featured">
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
);
}
async function AllPosts() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 }
}).then(r => r.json());
return (
<div className="posts">
{posts.map(post => (
View on GitHub