CPCODELAB
Next.js

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