Home Quizzes Leaderboard Competitions Learn Hire Us
About Contact
Log In Sign Up
Learn Next.js Data Fetching

Data Fetching

⏱ 15 min read read
Data Fetching in Server Components:

Server Components can be async --- you can await data directly in the
component body. No useEffect, no useState, no loading state needed.

// app/users/page.tsx --- Server Component

async function getUsers() {

const res = await fetch('https://api.example.com/users');

if (!res.ok) throw new Error('Failed to fetch');

return res.json();

}

export default async function UsersPage() {

const users = await getUsers(); // ← just await it!

return (

<ul>

{users.map((u) => <li key={u.id}>{u.name}</li>)}

</ul>

);

}

Next.js fetch() Cache Options:

{ cache: 'force-cache' } ← cache forever (SSG behaviour, default)

{ cache: 'no-store' } ← never cache (SSR behaviour, always fresh)

{ next: { revalidate: 60 } } ← ISR: re-fetch every 60 seconds

{ next: { tags: ['posts'] } } ← on-demand revalidation by tag

Route Segment Config:

You can also set caching at the route level using exported constants:

// Force this entire page to be dynamic (SSR)

export const dynamic = 'force-dynamic';

// Or revalidate every hour

export const revalidate = 3600;

Parallel Data Fetching:

// Fetch both at the same time --- don't await one then the other

const [user, posts] = await Promise.all([

fetchUser(id),

fetchPosts(id),

]);

NEVER fetch data in useEffect for data that can be fetched on the
server.

useEffect fetching causes a waterfall: HTML → JS → fetch → render.

Server Component fetching: HTML arrives with data already included.

Use 'use client' + SWR/React Query only for client-side dynamic
data.
Code Example
// app/posts/page.tsx --- SSG with ISR

type Post = { id: number; title: string; body: string };

async function getPosts(): Promise<Post[]> {

const res = await
fetch('https://jsonplaceholder.typicode.com/posts', {

next: { revalidate: 60 }, // re-fetch every 60 seconds (ISR)

});

if (!res.ok) throw new Error('Failed to fetch posts');

return res.json();

}

export default async function PostsPage() {

const posts = await getPosts();

return (

<main>

<h1>Posts ({posts.length})</h1>

{posts.slice(0, 5).map((post) => (

<article key={post.id}>

<h2>{post.title}</h2>

<p>{post.body.slice(0, 100)}...</p>

</article>

))}

</main>

);

}

// app/posts/[id]/page.tsx --- dynamic route with SSG

async function getPost(id: string): Promise<Post> {

const res = await
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {

cache: 'force-cache', // cache forever

});

return res.json();

}

export async function generateStaticParams() {

const posts = await
fetch('https://jsonplaceholder.typicode.com/posts').then(r =>
r.json());

return posts.slice(0, 10).map((p: Post) => ({ id: String(p.id) }));

}

export default async function PostPage({ params }: { params: { id:
string } }) {

const post = await getPost(params.id);

return (

<article>

<h1>{post.title}</h1>

<p>{post.body}</p>

</article>

);

}
← File-Based Routing API Routes & Route Handlers →

Log in to track your progress and earn badges as you complete lessons.

Log In to Track Progress