Next.js Data Fetching Cheatsheet
Basic Server Fetching
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
});
if (!res.ok) {
throw new Error('Failed to fetch posts');
}
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
{posts.map(post => (
<div key={post.id}>{post.title}</div>
))}
</div>
);
}
Fetch with Error Handling
async function getData() {
try {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
return await res.json();
} catch (error) {
console.error('Fetch error:', error);
return { error: 'Failed to fetch data' };
}
}
Multiple Data Sources
async function getPageData() {
const [posts, users, comments] = await Promise.all([
fetch('https://api.example.com/posts').then(res => res.json()),
fetch('https://api.example.com/users').then(res => res.json()),
fetch('https://api.example.com/comments').then(res => res.json())
]);
return { posts, users, comments };
}
Basic Client Fetching
'use client';
import { useState, useEffect } from 'react';
export default function ClientData() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const res = await fetch('/api/data');
const result = await res.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{/* render data */}</div>;
}
SWR for Client Fetching
'use client';
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
export default function SWRComponent() {
const { data, error, isLoading, mutate } = useSWR('/api/posts', fetcher, {
refreshInterval: 5000,
revalidateOnFocus: true
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{data?.map(post => (
<div key={post.id}>{post.title}</div>
))}
<button onClick={() => mutate()}>Refresh</button>
</div>
);
}
getStaticProps (Pages Router)
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await res.json();
return {
props: {
post
},
revalidate: 3600
};
}
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map(post => ({
params: { id: post.id.toString() }
}));
return {
paths,
fallback: 'blocking'
};
}
App Router Static Generation
async function getPost(id) {
const res = await fetch(`https://api.example.com/posts/${id}`, {
next: { revalidate: 3600 }
});
return res.json();
}
export default async function PostPage({ params }) {
const post = await getPost(params.id);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
Time-Based Revalidation
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 60 }
});
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<div>
<h1>Products</h1>
{products.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}
On-Demand Revalidation
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(request) {
const { path, tag } = await request.json();
if (path) {
revalidatePath(path);
}
if (tag) {
revalidateTag(tag);
}
return Response.json({ revalidated: true });
}
await fetch('/api/revalidate', {
method: 'POST',
body: JSON.stringify({ path: '/products' })
});
Basic API Route
export async function GET() {
const posts = await getPostsFromDatabase();
return Response.json(posts);
}
export async function POST(request) {
const data = await request.json();
const newPost = await createPost(data);
return Response.json(newPost, { status: 201 });
}
API Route with Caching
export const revalidate = 3600;
export async function GET() {
const data = await fetch('https://external-api.com/data', {
cache: 'force-cache'
});
return Response.json(await data.json());
}
Parallel vs Sequential
async function getParallelData() {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(res => res.json()),
fetch('/api/posts').then(res => res.json()),
fetch('/api/comments').then(res => res.json())
]);
return { users, posts, comments };
}
async function getSequentialData() {
const user = await fetch('/api/user/1').then(res => res.json());
const posts = await fetch(`/api/posts?userId=${user.id}`).then(res => res.json());
const comments = await fetch(`/api/comments?postId=${posts[0].id}`).then(res => res.json());
return { user, posts, comments };
}
Conditional Fetching
async function getConditionalData(userId) {
if (!userId) {
return { posts: [] };
}
const posts = await fetch(`/api/posts?userId=${userId}`, {
next: { tags: ['posts', `user-${userId}`] }
}).then(res => res.json());
return { posts };
}
Server-Side Error Handling
async function getDataWithFallback() {
try {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return await res.json();
} catch (error) {
return { error: true, message: 'Failed to fetch data' };
}
}
Client-Side Error Handling
'use client';
import { useState, useEffect } from 'react';
export default function DataComponent() {
const [state, setState] = useState({
data: null,
loading: true,
error: null
});
useEffect(() => {
async function fetchData() {
try {
setState(prev => ({ ...prev, loading: true }));
const res = await fetch('/api/data');
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const data = await res.json();
setState({ data, loading: false, error: null });
} catch (error) {
setState({ data: null, loading: false, error: error.message });
}
}
fetchData();
}, []);
if (state.loading) return <div>Loading...</div>;
if (state.error) return <div>Error: {state.error}</div>;
return <div>{/* render state.data */}</div>;
}
Request Deduplication
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
});
return res.json();
}
export default async function Page() {
const [posts1, posts2] = await Promise.all([
getPosts(),
getPosts()
]);
return <div>{/* content */}</div>;
}
Streaming with Suspense
import { Suspense } from 'react';
async function SlowData() {
await new Promise(resolve => setTimeout(resolve, 2000));
const data = await fetch('https://api.example.com/slow-data');
return data.json();
}
export default function Page() {
return (
<div>
<h1>Fast Content</h1>
<Suspense fallback={<div>Loading slow data...</div>}>
<SlowData />
</Suspense>
</div>
);
}
Cache Strategies
const staticData = await fetch('/api/static-data', {
next: { revalidate: 86400 }
});
const dynamicData = await fetch('/api/dynamic-data', {
cache: 'no-store'
});
const userData = await fetch('/api/user-data', {
cache: 'no-store',
headers: { 'Authorization': `Bearer ${token}` }
});
Error Boundaries
'use client';
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}
Loading States
export default function Loading() {
return (
<div className="loading">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
}