---
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
{json.title}
;
}
// 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
);
}
```
## Server Actions for Mutations
```typescript
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
try {
const res = await fetch('https://api.example.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, content })
});
if (!res.ok) {
throw new Error('Failed to create post');
}
const post = await res.json();
// Revalidate the posts page
revalidatePath('/posts');
return { success: true, post };
} catch (error) {
return { success: false, error: error.message };
}
}
export async function updatePost(id: string, formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await fetch(`https://api.example.com/posts/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, content })
});
revalidatePath(`/posts/${id}`);
revalidatePath('/posts');
}
export async function deletePost(id: string) {
await fetch(`https://api.example.com/posts/${id}`, {
method: 'DELETE'
});
revalidatePath('/posts');
}
// app/posts/new/page.tsx
import { createPost } from '../actions';
export default function NewPost() {
return (
);
}
// With client-side validation
'use client';
import { useFormState, useFormStatus } from 'react-dom';
import { createPost } from './actions';
function SubmitButton() {
const { pending } = useFormStatus();
return (
);
}
export default function CreatePostForm() {
const [state, formAction] = useFormState(createPost, null);
return (
);
}
```
## Request Memoization
```typescript
// Automatic deduplication within a single render pass
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`);
return res.json();
}
export default async function Page() {
// These calls are automatically deduplicated
const user1 = await getUser('1');
const user2 = await getUser('1'); // Uses cached result
const user3 = await getUser('1'); // Uses cached result
return
{user1.name}
;
}
// Works across component boundaries
async function UserHeader() {
const user = await getUser('1');
return {user.name};
}
async function UserProfile() {
const user = await getUser('1'); // Same request, deduplicated
return
{user.bio}
;
}
export default function Page() {
return (
);
}
// Manual caching with React cache
import { cache } from 'react';
const getUser = cache(async (id: string) => {
const res = await fetch(`https://api.example.com/users/${id}`);
return res.json();
});
// Now getUser is memoized across the entire request
```
## Database Queries
```typescript
// Direct database access in Server Components
import { db } from '@/lib/db';
export default async function Posts() {
const posts = await db.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 10
});
return (
);
}
```
## When to Use This Skill
Use nextjs-data-fetching when you need to:
- Build static sites with dynamic data
- Implement SSR for dynamic content
- Use ISR for best of both worlds
- Optimize for SEO and performance
- Cache and revalidate data
- Build e-commerce sites
- Create content-heavy applications
- Implement real-time updates
- Build scalable applications
- Handle large datasets efficiently
- Implement pagination and infinite scroll
- Optimize Core Web Vitals
## Best Practices
1. **Use static generation by default** - Leverage SSG for pages that can be
pre-rendered at build time for optimal performance.
2. **Implement ISR for frequently updated content** - Use time-based or on-demand
revalidation for dynamic content that doesn't need real-time updates.
3. **Cache API responses appropriately** - Set proper revalidate times based on
how frequently data changes.
4. **Use TypeScript for data types** - Define proper types for API responses and
database queries to catch errors early.
5. **Handle loading and error states** - Implement loading.tsx and error.tsx files
for better user experience.
6. **Implement proper revalidation strategies** - Use on-demand revalidation with
webhooks for immediate updates when data changes.
7. **Optimize for Core Web Vitals** - Use streaming and Suspense to improve
perceived performance and loading times.
8. **Use parallel data fetching** - Fetch independent data sources simultaneously
to reduce waterfall effects.
9. **Test data fetching patterns** - Verify caching behavior, revalidation, and
error handling in tests.
10. **Monitor performance metrics** - Track cache hit rates, revalidation
frequency, and page load times.
## Common Pitfalls
1. **Not caching data appropriately** - Using cache: 'no-store' for everything
defeats the performance benefits of SSG/ISR.
2. **Overusing SSR for static content** - Rendering static content on every
request wastes server resources.
3. **Not implementing error boundaries** - Missing error.tsx files cause poor
user experience when data fetching fails.
4. **Ignoring revalidation strategies** - Not setting revalidate times leads to
stale data or too many unnecessary requests.
5. **Not handling race conditions** - Parallel requests without proper ordering
can cause inconsistent UI state.
6. **Missing loading states** - Not implementing loading.tsx or Suspense boundaries
creates jarring loading experiences.
7. **Not optimizing bundle size** - Fetching too much data or including unnecessary
fields increases payload size.
8. **Exposing sensitive API keys** - Accidentally exposing secrets in client
components or client-side fetches.
9. **Not testing edge cases** - Skipping tests for error states, empty data, and
loading states leads to poor UX.
10. **Misunderstanding caching behavior** - Not knowing when Next.js caches requests
can lead to stale data or performance issues.
## Resources
- [Next.js Data Fetching](https://nextjs.org/docs/app/building-your-application/data-fetching)
- [Caching in Next.js](https://nextjs.org/docs/app/building-your-application/caching)
- [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations)
- [Incremental Static Regeneration](https://nextjs.org/docs/app/building-your-application/data-fetching/incremental-static-regeneration)
- [Revalidating Data](https://nextjs.org/docs/app/building-your-application/data-fetching/revalidating)
- [Fetching Patterns](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching)