Fetching Data in Server Components
10 min read
Data Fetching Without useEffect
Because Server Components are async functions, you can await any promise directly inside them — a fetch call, a database query, or a file read. There is no need for useEffect, useState, or a loading spinner for the initial data.
tsx
// src/app/blog/page.tsx
type Post = { id: number; title: string; body: string };
async function getPosts(): Promise<Post[]> {
const res = await fetch("https://jsonplaceholder.typicode.com/posts", {
next: { revalidate: 3600 }, // revalidate every hour
});
if (!res.ok) throw new Error("Failed to fetch posts");
return res.json();
}
export default async function BlogPage() {
const posts = await getPosts();
return (
<main>
<h1>Blog</h1>
<ul>
{posts.slice(0, 10).map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
);
}You can also query a database directly — for example using Prisma — without exposing credentials to the client, because this code never leaves the server.
tsx
import { db } from "@/lib/db";
export default async function UsersPage() {
const users = await db.user.findMany();
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}Never import server-only modules (like prisma or fs) into a Client Component. Next.js will throw a build error if you do, but it is always good to understand why — those modules would expose secrets and server internals to the browser.
Ready to test yourself?
Practice Next.js— quiz & coding exercises