Fetching data in server components
In the React course data was loaded in useEffect in the browser: first an empty page, then a request, then the data. A server component makes this simpler — you get the data with await right in the component body, and the browser receives a page that already contains it.
Data from your own functions
The most common case is calling a function that reads the database. In the blog that is getPosts() from lib/posts.ts:
// app/page.tsx
import Link from 'next/link';
import { getPosts } from '@/lib/posts';
export default async function HomePage() {
const posts = await getPosts();
return (
<main>
<h1>My blog</h1>
{posts.map((post) => (
<p key={post.slug}>
<Link href={`/posts/${post.slug}`}>{post.title}</Link>
</p>
))}
</main>
);
}The home page shows the list of posts. Open the page source in the browser (Ctrl+U) — the post titles are already in the HTML.
Data from an external API
For a request to someone else's service, use the ordinary fetch. Let us take the JSONPlaceholder practice service:
// app/external/page.tsx
type ExternalPost = { id: number; title: string };
export default async function ExternalPage() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=3');
const posts: ExternalPost[] = await response.json();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}The /external address shows three titles. The server makes the request, so CORS restrictions do not get in the way, and an API key, if needed, never reaches the browser.
Several requests in parallel
Two await lines in a row wait for each other. If the requests are independent, start them at once with Promise.all:
// app/posts/[slug]/page.tsx (fragment)
const [post, comments] = await Promise.all([getPost(slug), getComments(slug)]);The post and its comments load at the same time, and the page is ready sooner. The getComments function arrives together with the database.
Handling an error
If the external service responds with an error, check response.ok and throw an exception:
// app/external/page.tsx (fragment)
if (!response.ok) {
throw new Error('Could not load posts');
}The exception shows the nearest error.tsx file — see the error handling lesson.
What about client components?
A client component cannot be async. Its data is usually loaded in a server parent and passed down through props. For data that changes right in the browser, useEffect or libraries such as SWR and TanStack Query still fit.
Common mistake
Requesting the app's own API from a server component: fetch('/api/posts'). On the server a relative address does not work, and the extra HTTP request is unnecessary anyway. Call the same function the API uses directly: await getPosts().