Skip to main content

nextjs-data-fetching

Use when next.js data fetching patterns including SSG, SSR, and ISR. Use when building data-driven Next.js applications.

Source facts

Repository
TheBushidoCollective/han
Last source activity
February 11, 2026 at 17:47
Detected SKILL.md language
English
Stars
198
Forks
22

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
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
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub