💡 Key Takeaways
⚡ Server-Side Fetching: Use async/await in server components or API routes.
🌐 Client-Side Fetching: useEffect, useState, or SWR for dynamic UI data.
🏗️ SSG & ISR: Pre-render pages with getStaticProps() and revalidate with revalidate.
🧩 Patterns & Error Handling: Parallel, sequential, conditional fetching; wrap in try/catch.
🚀 Performance: Deduplicate requests, leverage caching, Suspense, and lazy loading.
⚡ Server-Side Fetching
- Fetch data in server components or API routes
- Handle multiple data sources in parallel
- Wrap fetch in try/catch for errors
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(p => <div key={p.id}>{p.title}</div>)}</div>;
}
🌐 Client-Side Fetching
- Use
useEffect+useStatefor fetching on the client - Handle loading, error, and data states
- SWR simplifies caching and revalidation
'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');
setData(await res.json());
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{JSON.stringify(data)}</div>;
}
🏗️ Static Site Generation (SSG) & ISR
- Pre-render pages using server-side fetch in
getStaticProps() - Enable ISR with
revalidateseconds - Use on-demand revalidation with API routes
export async function getStaticProps({ params }) {
const post = await fetch(`/api/posts/${params.id}`, { next: { revalidate: 3600 } }).then(r => r.json());
return { props: { post }, revalidate: 3600 };
}
// On-demand revalidation
export async function POST(req) {
const { path } = await req.json();
if (path) revalidatePath(path);
return Response.json({ revalidated: true });
}
🛠 API Routes & Caching
- Use
GETandPOSThandlers for API routes - Cache responses with
revalidateorcache: 'force-cache' - Apply
revalidatestrategically for static vs dynamic data
export async function GET() {
const data = await getPostsFromDB();
return Response.json(data);
}
export const revalidate = 3600;
const data = await fetch('/api/data', { cache: 'force-cache' }).then(r => r.json());
🔄 Fetching Patterns
- Parallel: fetch independent endpoints with
Promise.all() - Sequential: fetch dependent resources step-by-step
- Conditional: fetch only if parameters exist
if (userId) {
const posts = await fetch(`/api/posts?userId=${userId}`).then(r => r.json());
}
🚨 Error Handling
- Server-side: try/catch around fetch
- Client-side: state for
loading,error, anddata - Always provide fallback UI
export default function Error({ error }) { return <div>{error.message}</div>; }
export default function Loading() { return <div>Loading...</div>; }
⚡ Performance Tips
- Deduplicate identical fetches automatically handled by Next.js
- Stream heavy data using
Suspenseand lazy components - Optimize ISR and caching for static + dynamic content
import { Suspense } from 'react';
<Suspense fallback={<div>Loading...</div>}><SlowData /></Suspense>