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.

소스 정보

저장소
TheBushidoCollective/han
최근 소스 활동
2026년 2월 11일 17:47
감지된 SKILL.md 언어
영어
스타
198
포크
22

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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 => (
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기