plugins/nextjs-master/skills/nextjs-data-fetching/SKILL.md
Complete Next.js data fetching system (Next.js 15.5/16). PROACTIVELY activate for: (1) Server Component data fetching, (2) Parallel and sequential fetching, (3) Streaming with Suspense, (4) Route Handlers (API routes), (5) Client-side fetching with SWR/TanStack Query, (6) generateStaticParams for static generation, (7) Revalidation strategies, (8) Error handling for data, (9) Async params/searchParams (Next.js 16), (10) Cache Components with 'use cache'. Provides: Fetch patterns, caching options, streaming UI, API route handlers, client fetching setup, async params pattern. Ensures optimal data loading with proper caching and error handling.
npx skillsauth add JosiahSiegel/claude-plugin-marketplace nextjs-data-fetchingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Pattern | Code | Purpose |
|---------|------|---------|
| Server fetch | await fetch(url) | Cached by default |
| No cache | { cache: 'no-store' } | Always fresh |
| Revalidate | { next: { revalidate: 60 } } | Time-based refresh |
| Tags | { next: { tags: ['posts'] } } | Tag-based invalidation |
| Config | Value | Effect |
|--------|-------|--------|
| dynamic | 'force-dynamic' | Always SSR |
| revalidate | 60 | ISR every 60s |
| fetchCache | 'force-no-store' | No caching |
| Client Library | Hook | Use Case |
|----------------|------|----------|
| SWR | useSWR(key, fetcher) | Simple client fetching |
| TanStack Query | useQuery({ queryKey, queryFn }) | Complex state/mutations |
Use for data loading patterns:
Related skills:
nextjs-cachingnextjs-server-actionsnextjs-app-router// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
if (!res.ok) throw new Error('Failed to fetch posts');
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<ul>
{posts.map((post: Post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
// app/users/page.tsx
import { db } from '@/lib/db';
export default async function UsersPage() {
// Direct database query - no API needed
const users = await db.users.findMany({
orderBy: { createdAt: 'desc' },
take: 10,
});
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// app/dashboard/page.tsx
async function getUser() {
const res = await fetch('https://api.example.com/user');
return res.json();
}
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
return res.json();
}
async function getAnalytics() {
const res = await fetch('https://api.example.com/analytics');
return res.json();
}
export default async function DashboardPage() {
// Fetch all data in parallel
const [user, posts, analytics] = await Promise.all([
getUser(),
getPosts(),
getAnalytics(),
]);
return (
<div>
<UserCard user={user} />
<PostsList posts={posts} />
<AnalyticsChart data={analytics} />
</div>
);
}
// When data depends on previous request
async function getUser(userId: string) {
const res = await fetch(`https://api.example.com/users/${userId}`);
return res.json();
}
async function getUserPosts(userId: string) {
const res = await fetch(`https://api.example.com/users/${userId}/posts`);
return res.json();
}
export default async function UserPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
// Sequential: user first, then posts
const user = await getUser(id);
const posts = await getUserPosts(user.id);
return (
<div>
<h1>{user.name}</h1>
<PostsList posts={posts} />
</div>
);
}
// Cached by default (equivalent to cache: 'force-cache')
const data = await fetch('https://api.example.com/data');
// Opt out of caching
const data = await fetch('https://api.example.com/data', {
cache: 'no-store',
});
// Time-based revalidation
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }, // Revalidate every hour
});
// Tag-based revalidation
const data = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] },
});
// app/posts/page.tsx
export const dynamic = 'force-dynamic'; // Always dynamic
// or
export const revalidate = 60; // Revalidate every 60 seconds
// or
export const fetchCache = 'force-no-store'; // Don't cache any fetches
// app/actions.ts
'use server';
import { revalidatePath, revalidateTag } from 'next/cache';
export async function createPost(formData: FormData) {
await db.posts.create({
data: { title: formData.get('title') as string },
});
// Revalidate specific path
revalidatePath('/posts');
// Or revalidate by tag
revalidateTag('posts');
}
// app/dashboard/page.tsx
import { Suspense } from 'react';
async function SlowComponent() {
const data = await fetch('https://api.example.com/slow-data');
return <div>{/* render data */}</div>;
}
async function FastComponent() {
const data = await fetch('https://api.example.com/fast-data');
return <div>{/* render data */}</div>;
}
export default function DashboardPage() {
return (
<div>
{/* Fast component renders immediately */}
<Suspense fallback={<FastSkeleton />}>
<FastComponent />
</Suspense>
{/* Slow component streams in when ready */}
<Suspense fallback={<SlowSkeleton />}>
<SlowComponent />
</Suspense>
</div>
);
}
// app/page.tsx
import { Suspense } from 'react';
export default function Page() {
return (
<Suspense fallback={<PageSkeleton />}>
<Header />
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent />
<Suspense fallback={<CommentsSkeleton />}>
<Comments />
</Suspense>
</Suspense>
</Suspense>
);
}
'use client';
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then((res) => res.json());
export function UserProfile({ userId }: { userId: string }) {
const { data, error, isLoading, mutate } = useSWR(
`/api/users/${userId}`,
fetcher
);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading user</div>;
return (
<div>
<h1>{data.name}</h1>
<button onClick={() => mutate()}>Refresh</button>
</div>
);
}
// providers/query-provider.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
export function QueryProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
},
})
);
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
}
// components/Posts.tsx
'use client';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
async function fetchPosts() {
const res = await fetch('/api/posts');
return res.json();
}
async function createPost(data: { title: string }) {
const res = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(data),
});
return res.json();
}
export function Posts() {
const queryClient = useQueryClient();
const { data: posts, isLoading } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
});
const mutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
if (isLoading) return <div>Loading...</div>;
return (
<div>
<ul>
{posts?.map((post: Post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
<button onClick={() => mutation.mutate({ title: 'New Post' })}>
Add Post
</button>
</div>
);
}
// app/api/posts/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const posts = await db.posts.findMany();
return NextResponse.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await db.posts.create({ data: body });
return NextResponse.json(post, { status: 201 });
}
// app/api/posts/[id]/route.ts
import { NextResponse } from 'next/server';
interface RouteParams {
params: Promise<{ id: string }>;
}
export async function GET(request: Request, { params }: RouteParams) {
const { id } = await params;
const post = await db.posts.findUnique({ where: { id } });
if (!post) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
return NextResponse.json(post);
}
export async function PUT(request: Request, { params }: RouteParams) {
const { id } = await params;
const body = await request.json();
const post = await db.posts.update({
where: { id },
data: body,
});
return NextResponse.json(post);
}
export async function DELETE(request: Request, { params }: RouteParams) {
const { id } = await params;
await db.posts.delete({ where: { id } });
return new NextResponse(null, { status: 204 });
}
// app/api/search/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const query = searchParams.get('q');
const page = parseInt(searchParams.get('page') || '1');
const results = await search(query, page);
return NextResponse.json(results);
}
// app/api/auth/route.ts
import { NextResponse } from 'next/server';
import { cookies, headers } from 'next/headers';
export async function GET() {
const cookieStore = await cookies();
const token = cookieStore.get('token');
const headersList = await headers();
const authorization = headersList.get('authorization');
// Set cookie in response
const response = NextResponse.json({ success: true });
response.cookies.set('session', 'value', {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 60 * 60 * 24, // 1 day
});
return response;
}
// app/posts/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await db.posts.findMany({ select: { slug: true } });
return posts.map((post) => ({
slug: post.slug,
}));
}
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await db.posts.findUnique({ where: { slug } });
return <article>{post?.content}</article>;
}
// Allow dynamic paths beyond generateStaticParams
export const dynamicParams = true; // default
// 404 for paths not in generateStaticParams
export const dynamicParams = false;
async function getData() {
const res = await fetch('https://api.example.com/data');
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
try {
const data = await getData();
return <div>{/* render data */}</div>;
} catch (error) {
return <div>Error loading data</div>;
}
}
// app/posts/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Error loading posts</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
| Practice | Description | |----------|-------------| | Fetch in Server Components | Avoid client-side fetching when possible | | Use parallel fetching | Promise.all for independent data | | Implement streaming | Suspense for progressive loading | | Cache appropriately | Use revalidate or tags for fresh data | | Handle errors | Use error.tsx boundaries | | Type your data | Use TypeScript for API responses |
development
This skill should be used when the user asks to train, debug, scale, or improve ML models. PROACTIVELY activate for: (1) PyTorch, TensorFlow/Keras, JAX, Flax, Hugging Face Trainer/Accelerate training loops, (2) distributed training, DDP/FSDP/DeepSpeed, TPU/GPU setup, (3) mixed precision AMP/bf16, gradient accumulation, checkpointing, seeding, (4) overfitting, imbalance, loss functions, regularization, LR schedules, warmup, (5) memory optimization, gradient checkpointing, offloading, quantization-aware training. Provides: reproducible training best practices across deep learning and classical ML.
development
This skill should be used when the user asks to productionize, track, version, govern, monitor, or automate ML systems. PROACTIVELY activate for: (1) MLflow, Weights & Biases, Neptune, Comet, ClearML experiment tracking, (2) model registry, model versioning, artifact lineage, reproducibility, (3) Kubeflow, SageMaker Pipelines, Vertex AI Pipelines, Azure ML pipelines, Databricks workflows, (4) CI/CD, continuous training/evaluation, A/B tests, canary/shadow deployments, (5) drift detection, model monitoring, data validation, responsible AI governance. Provides: end-to-end MLOps architecture and operational safeguards.
development
This skill should be used when the user asks to optimize, export, serve, compress, or accelerate ML inference. PROACTIVELY activate for: (1) latency, throughput, p95/p99, batching, concurrency, KV cache, memory, or cost issues, (2) quantization INT8/INT4, GPTQ, AWQ, bitsandbytes, pruning, sparsity, distillation, (3) ONNX export, ONNX Runtime, TensorRT, TorchScript, torch.compile, XLA, OpenVINO, Core ML, TFLite, (4) Triton, TorchServe, TF Serving, BentoML, Seldon, KServe configuration, (5) edge deployment, CPU/GPU/TPU/Inferentia serving. Provides: hardware-aware inference optimization and safe benchmarking.
testing
This skill should be used when the user asks to tune hyperparameters, run sweeps, optimize search spaces, or use AutoML. PROACTIVELY activate for: (1) Optuna, Ray Tune, FLAML, AutoGluon, Hyperopt, Nevergrad, KerasTuner, W&B sweeps, (2) grid search, random search, Bayesian optimization, TPE, Gaussian processes, evolutionary search, (3) ASHA, Hyperband, successive halving, multi-fidelity optimization, population-based training, (4) learning-rate finder, batch-size search, early stopping, pruning, (5) reproducible sweep design and experiment analysis. Provides: budget-aware hyperparameter search strategy.