Next.js Data Fetching — fetch, cache, revalidate
Advertisement
Next.js Data Fetching — fetch, cache, revalidate
Next.js extends the Fetch API with powerful caching and revalidation features, giving you fine-grained control over data freshness.
- Next.js Data Fetching — fetch, cache, revalidate
- The Fetch API with Next.js
- Caching Strategies
- Static Caching (Default)
- Time-Based Revalidation
- No Caching
- On-Demand Revalidation
- Revalidate Tags
- Advanced Fetching Patterns
- Parallel Data Fetching
- Sequential Data Fetching
- Custom Fetch Wrapper
- Error Handling and Fallbacks
- Background Revalidation
- Performance Monitoring
- FAQ
The Fetch API with Next.js
Next.js extends fetch with built-in caching:
// app/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // Cache for 1 hour
})
return res.json()
}
export default async function Page() {
const posts = await getPosts()
return <div>{posts.map(p => <h2 key={p.id}>{p.title}</h2>)}</div>
}
Caching Strategies
Static Caching (Default)
// Cached by default until manually revalidated
async function getStaticData() {
const res = await fetch('https://api.example.com/config')
return res.json()
}
Time-Based Revalidation
// Cache for 60 seconds
async function getFreshPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 }
})
return res.json()
}
No Caching
// Fetch fresh on every request
async function getRealTimeData() {
const res = await fetch('https://api.example.com/live', {
next: { revalidate: 0 }
})
return res.json()
}
On-Demand Revalidation
Revalidate specific data when needed using Server Actions:
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function updatePost(id: string, data: any) {
// Update in database
await db.posts.update({ where: { id }, data })
// Revalidate the specific page
revalidatePath(`/blog/${id}`)
}
// app/blog/[id]/edit/page.tsx
'use client'
import { updatePost } from '@/app/actions'
export function EditPostForm({ postId }: { postId: string }) {
async function handleSubmit(formData: FormData) {
await updatePost(postId, {
title: formData.get('title'),
content: formData.get('content')
})
}
return (
<form action={handleSubmit}>
<input name="title" />
<textarea name="content" />
<button type="submit">Save</button>
</form>
)
}
Revalidate Tags
Use tags to revalidate related data:
// lib/api.ts
async function getPost(id: string) {
const res = await fetch(`https://api.example.com/posts/${id}`, {
next: { tags: ['post', `post-${id}`] }
})
return res.json()
}
async function getAuthor(authorId: string) {
const res = await fetch(`https://api.example.com/authors/${authorId}`, {
next: { tags: [`author-${authorId}`] }
})
return res.json()
}
// app/actions.ts
'use server'
import { revalidateTag } from 'next/cache'
export async function updateAuthorName(authorId: string, name: string) {
await db.authors.update({ where: { id: authorId }, data: { name } })
// Revalidate all posts by this author
revalidateTag(`author-${authorId}`)
}
Advanced Fetching Patterns
Parallel Data Fetching
export default async function DashboardPage() {
// Fetch in parallel
const [users, posts, analytics] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/analytics').then(r => r.json())
])
return (
<div>
<UserList users={users} />
<PostList posts={posts} />
<Analytics data={analytics} />
</div>
)
}
Sequential Data Fetching
export default async function PostPage({ params }) {
// First fetch
const post = await fetch(`/api/posts/${params.id}`).then(r => r.json())
// Then fetch related data
const author = await fetch(`/api/authors/${post.authorId}`).then(r => r.json())
const comments = await fetch(`/api/posts/${params.id}/comments`).then(r => r.json())
return (
<article>
<h1>{post.title}</h1>
<p>By {author.name}</p>
<Comments items={comments} />
</article>
)
}
Custom Fetch Wrapper
Create a reusable fetch wrapper:
// lib/fetch.ts
export async function fetchAPI(
endpoint: string,
options: {
cache?: 'no-store' | 'force-cache'
next?: { revalidate?: number; tags?: string[] }
} = {}
) {
const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com'
const url = `${baseUrl}${endpoint}`
try {
const res = await fetch(url, {
headers: {
'Authorization': `Bearer ${process.env.API_KEY}`
},
...options
})
if (!res.ok) throw new Error(`API error: ${res.status}`)
return res.json()
} catch (error) {
console.error(`Fetch failed for ${endpoint}:`, error)
throw error
}
}
Usage:
const data = await fetchAPI('/posts', {
next: { revalidate: 60, tags: ['posts'] }
})
Error Handling and Fallbacks
export default async function Page() {
try {
const data = await fetch('/api/data', { next: { revalidate: 60 } })
.then(r => {
if (!r.ok) throw new Error('Failed to fetch')
return r.json()
})
return <div>{/* render data */}</div>
} catch (error) {
return <div>Failed to load data. Please try again later.</div>
}
}
Background Revalidation
Using ISR (Incremental Static Regeneration):
// app/blog/[slug]/page.tsx
export const revalidate = 60 // Revalidate every 60 seconds
export async function generateStaticParams() {
const posts = await fetch('/api/posts').then(r => r.json())
return posts.map(post => ({ slug: post.slug }))
}
export default async function PostPage({ params }) {
const post = await fetch(`/api/posts/${params.slug}`).then(r => r.json())
return <article>{post.content}</article>
}
Performance Monitoring
Track fetch performance:
async function fetchWithTiming(url: string) {
const start = performance.now()
const res = await fetch(url)
const duration = performance.now() - start
console.log(`Fetch ${url} took ${duration}ms`)
return res.json()
}
FAQ
Q: What's the difference between revalidate and no-store? A: revalidate: 60 caches for 60 seconds then checks for updates. no-store never caches, always fetches fresh.
Q: Can I revalidate all pages at once? A: Use revalidateTag() with a broad tag, or revalidatePath('/', 'layout') to revalidate the entire app.
Q: How do I handle sensitive data like API keys in fetch? A: Use environment variables accessed only on the server, never exposed to the client. Next.js Server Components run on the server, so this is safe.
Master data fetching and caching for optimal performance and freshness.
Advertisement