nextjs-data-fetching
giuseppe-trisciuoglio/developer-kit
Next.js App Router data fetching patterns: server/client strategies, SWR, React Query, ISR, and revalidation.
What is nextjs-data-fetching?
Provides patterns for data fetching in Next.js App Router including server-side fetching, client-side libraries (SWR/React Query), Incremental Static Regeneration (ISR), revalidation strategies, error boundaries, and loading states. Use when implementing data fetching, choosing between server and client components, setting up caching strategies, or handling loading and error states.
- Server Component data fetching with async/await directly in components
- Parallel data fetching using Promise.all() for independent requests
- Time-based and on-demand revalidation with ISR and cache tags
- SWR and React Query integration for client-side data fetching and caching
- Error boundaries and Suspense-based loading states for graceful error handling
- Server Actions for mutations with automatic cache revalidation
How to install nextjs-data-fetching
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nextjs-data-fetching- Next.js 13+ with App Router
- npm or yarn for installing optional libraries (swr, @tanstack/react-query)
How to use nextjs-data-fetching
- 1.Choose between Server Components (default for better performance) or Client Components based on your needs
- 2.For server-side fetching, use async/await directly in Server Components and configure caching with next.revalidate or cache options
- 3.For parallel independent requests, wrap multiple fetch calls in Promise.all()
- 4.For client-side data fetching, install and configure SWR or React Query with appropriate stale time and refetch options
- 5.Implement loading states using loading.tsx files or Suspense boundaries with fallback UI
- 6.Set up error boundaries to gracefully handle data fetching failures in Client Components
- 7.Use Server Actions for form submissions and mutations that require cache revalidation
Use cases
- Building a blog with ISR that updates posts every hour without rebuilding
- Fetching user-specific dashboard data in parallel (user, posts, analytics) in a Server Component
- Implementing real-time data updates with SWR for interactive client-side features
- Setting up on-demand cache revalidation via API endpoint when content changes
- Handling loading and error states with Suspense boundaries and error boundaries
- Next.js App Router developers
- Full-stack engineers building data-driven applications
- Teams implementing caching and performance optimization strategies
- Developers working with static generation, ISR, and dynamic rendering
nextjs-data-fetching FAQ
Default to Server Components for better performance and security—fetch directly in async Server Components. Use Client Components only when you need interactivity, hooks, or real-time updates with SWR/React Query.
Use Promise.all() to fetch independent requests in parallel, reducing total latency. Only use sequential fetching when later requests depend on earlier results.
Time-based revalidation (ISR) regenerates pages at fixed intervals (e.g., every 60 seconds). On-demand revalidation uses revalidateTag() to immediately invalidate cache when content changes, triggered by API calls or webhooks.
Use loading.tsx files for automatic loading UI, or wrap components in Suspense boundaries with fallback content. Wrap client-side data fetching in Error Boundaries to catch and display errors gracefully.
Both are suitable for client-side caching. SWR is lighter and simpler for basic use cases; React Query offers more advanced features like mutations, optimistic updates, and infinite queries. Choose based on complexity needs.
Full instructions (SKILL.md)
Source of truth, from giuseppe-trisciuoglio/developer-kit.
name: nextjs-data-fetching description: Provides Next.js App Router data fetching patterns including SWR and React Query integration, parallel data fetching, Incremental Static Regeneration (ISR), revalidation strategies, and error boundaries. Use when implementing data fetching in Next.js applications, choosing between server and client fetching, setting up caching strategies, or handling loading and error states. allowed-tools: Read, Write, Edit, Bash
Next.js Data Fetching
Overview
Provides patterns for data fetching in Next.js App Router: server-side fetching, SWR/React Query integration, ISR, revalidation, error boundaries, and loading states.
When to Use
- Implementing data fetching in Next.js App Router
- Choosing between Server Components and Client Components
- Setting up SWR or React Query for client-side caching
- Configuring ISR, time-based, or on-demand revalidation
- Handling loading and error states
- Building forms with Server Actions
Instructions
Server Component Fetching
Fetch directly in async Server Components:
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) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
Parallel Data Fetching
Use Promise.all() for independent requests:
async function getDashboardData() {
const [user, posts, analytics] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/analytics').then(r => r.json()),
]);
return { user, posts, analytics };
}
export default async function DashboardPage() {
const { user, posts, analytics } = await getDashboardData();
// Render dashboard
}
Sequential Data Fetching (When Dependencies Exist)
async function getUserPosts(userId: string) {
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
const posts = await fetch(`/api/users/${userId}/posts`).then(r => r.json());
return { user, posts };
}
Time-based Revalidation (ISR)
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 } // Revalidate every 60 seconds
});
return res.json();
}
On-Demand Revalidation
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const tag = request.nextUrl.searchParams.get('tag');
if (tag) {
revalidateTag(tag);
return Response.json({ revalidated: true });
}
return Response.json({ revalidated: false }, { status: 400 });
}
Tag data for selective revalidation:
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'], revalidate: 3600 }
});
return res.json();
}
Opt-out of Caching
async function getRealTimeData() {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
return res.json();
}
// Or:
export const dynamic = 'force-dynamic';
Client-Side Data Fetching
SWR Integration
Install: npm install swr
'use client';
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(r => r.json());
export function Posts() {
const { data, error, isLoading } = useSWR('/api/posts', fetcher, {
refreshInterval: 5000,
revalidateOnFocus: true,
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Failed to load posts</div>;
return (
<ul>
{data.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
React Query Integration
Install: npm install @tanstack/react-query
// app/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
refetchOnWindowFocus: false,
},
},
}));
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
See react-query.md for mutations, optimistic updates, infinite queries, and advanced patterns.
Error Boundaries
Wrap client-side data fetching in Error Boundaries to handle failures gracefully:
See error-boundaries.md for full ErrorBoundary implementations (basic, with reset callback) and usage examples with data fetching.
Server Actions
Use Server Actions for mutations with cache revalidation:
See server-actions.md for complete examples including form validation with useActionState, error handling, and cache invalidation.
Loading States
loading.tsx Pattern
// app/posts/loading.tsx
export default function PostsLoading() {
return (
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-16 bg-gray-200 animate-pulse rounded" />
))}
</div>
);
}
Suspense Boundaries
// app/posts/page.tsx
import { Suspense } from 'react';
import { PostsList } from './PostsList';
import { PostsSkeleton } from './PostsSkeleton';
export default function PostsPage() {
return (
<div>
<h1>Posts</h1>
<Suspense fallback={<PostsSkeleton />}>
<PostsList />
</Suspense>
</div>
);
}
Best Practices
- Default to Server Components — Fetch in Server Components for better performance
- Use parallel fetching —
Promise.all()for independent requests to reduce latency - Choose appropriate caching:
- Static data: long revalidation intervals
- Dynamic data: short revalidation or
cache: 'no-store' - User-specific data: use dynamic rendering
- Handle errors gracefully — Wrap client data fetching in error boundaries
- Implement loading states — Use
loading.tsxor Suspense boundaries - Prefer SWR/React Query for: real-time data, user interactions, background updates
- Use Server Actions for: form submissions, mutations requiring cache revalidation
Constraints and Warnings
Critical Constraints
- Server Components cannot use hooks (
useState,useEffect) or client data fetching libraries - Client Components must include the
'use client'directive - The
fetchAPI in Next.js extends standard Web fetch with Next.js-specific caching options - Server Actions require
'use server'and can only be called from Client Components or form actions
Common Pitfalls
- Fetching in loops — Avoid sequential fetches in Server Components; use parallel fetching
- Cache poisoning — Do not use
force-cachefor user-specific or personalized data - Memory leaks — Clean up subscriptions in Client Components when using real-time data
- Hydration mismatches — Ensure server and client render the same initial state with React Query hydration
Examples
Example 1: Blog with ISR
Input: Create a blog page that fetches posts and updates every hour.
// app/blog/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
});
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts();
return (
<main>
<h1>Blog Posts</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
);
}
Output: Page statically generated at build time, revalidated every hour.
Example 2: Dashboard with Parallel Fetching
Input: Build a dashboard showing user profile, stats, and recent activity in parallel.
// app/dashboard/page.tsx
async function getDashboardData() {
const [user, stats, activity] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/stats').then(r => r.json()),
fetch('/api/activity').then(r => r.json()),
]);
return { user, stats, activity };
}
export default async function DashboardPage() {
const { user, stats, activity } = await getDashboardData();
return (
<div className="dashboard">
<UserProfile user={user} />
<StatsCards stats={stats} />
<ActivityFeed activity={activity} />
</div>
);
}
Output: All three requests execute concurrently, minimizing total load time.
Related skills
More from giuseppe-trisciuoglio/developer-kit and the wider catalog.

nextjs-deployment
Deploy Next.js apps to production with Docker, CI/CD pipelines, health checks, and OpenTelemetry monitoring.

nextjs-performance
Expert Next.js performance optimization for Core Web Vitals, images, fonts, caching, and Server Components.

notebooklm
Query Google NotebookLM notebooks and manage research sources via CLI for RAG-powered documentation retrieval.

nx-monorepo
Comprehensive Nx monorepo management for TypeScript/JavaScript projects with workspace creation, generators, and CI/CD integration.

pr-review-comments
Post review findings as inline comments on GitHub PRs, anchored to file and line.

prompt-engineering
Design, debug, and optimize LLM prompts with few-shot learning, chain-of-thought, and template systems.