Next.js: The React Framework (App Router)Entry 6 of 16
Fetching Data on the Server
A Server Component can be async and await data directly: from a database, an internal function, or fetch.
Notes
- No
useEffect, no loading state in the component, no API route needed: the data is in the HTML. - Keep data access in a separate module (
lib/ordata/) marked withimport 'server-only'so it can never end up in browser code. - Request data such as
cookies()andheaders()is async and makes the route dynamic (rendered per request). - Independent requests should run in parallel with
Promise.all, not oneawaitafter another. - Client Components still fetch client-side when data depends on user interaction; TanStack Query is the usual choice there.
What / Why / When
- What
Data loaded during rendering on the server.
- Why
No client waterfall, secrets stay on the server, the HTML arrives complete.
- When
Data needed for the first render of a page.
- Use case
A posts list page.
Code compare
// lib/posts.tsimport "server-only";export type Post = { id: string; slug: string; title: string; body: string };export async function getPosts(): Promise<Post[]> { const res = await fetch(`${process.env.API_URL}/posts`); if (!res.ok) throw new Error("Failed to load posts"); return res.json();}// app/posts/page.tsximport Link from "next/link";import { cookies } from "next/headers";import { getPosts } from "@/lib/posts";export default async function PostsPage() { const theme = (await cookies()).get("theme")?.value ?? "light"; const posts = await getPosts(); return ( <ul className={theme}> {posts.map((post) => ( <li key={post.id}> <Link href={`/posts/${post.slug}`}>{post.title}</Link> </li> ))} </ul> );}Data access lives in a service. With SSR, the request runs on the server, and provideClientHydration() passes the response to the browser (HTTP transfer cache) so it isn't fetched twice:
// post.service.tsimport { Injectable, inject } from "@angular/core";import { HttpClient } from "@angular/common/http";import { environment } from "../environments/environment";export type Post = { id: string; slug: string; title: string; body: string };@Injectable({ providedIn: "root" })export class PostService { private http = inject(HttpClient); getPosts() { return this.http.get<Post[]>(`${environment.apiUrl}/posts`); } getPost(slug: string) { return this.http.get<Post>(`${environment.apiUrl}/posts/${slug}`); }}// posts.component.tsimport { Component, inject } from "@angular/core";import { toSignal } from "@angular/core/rxjs-interop";import { RouterLink } from "@angular/router";import { PostService } from "./post.service";@Component({ selector: "app-posts", imports: [RouterLink], template: ` <ul> @for (post of posts(); track post.id) { <li> <a [routerLink]="['/posts', post.slug]">{{ post.title }}</a> </li> } </ul> `,})export class PostsComponent { posts = toSignal(inject(PostService).getPosts(), { initialValue: [] });}// app.config.tsproviders: [provideHttpClient(withFetch()), provideClientHydration()];Differences and gotchas
- Next.js Server Components can query a database directly. Angular components always go through
HttpClientto a backend (for example, your Go API). - Angular renders on the server too, but the component code also ships to the browser to hydrate. Next.js Server Components ship none.
- Server-side request data in Angular: the
REQUESTtoken from@angular/core(null in the browser and at build time).
Quick reference
| Next.js | Angular |
|---|---|
async Server Component + await | Service + toSignal() / rxResource |
import 'server-only' | Backend API (code never in the app) |
cookies(), headers() | REQUEST token |
| Data in HTML, no refetch | HTTP transfer cache (provideClientHydration) |