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/ or data/) marked with import 'server-only' so it can never end up in browser code.
  • Request data such as cookies() and headers() is async and makes the route dynamic (rendered per request).
  • Independent requests should run in parallel with Promise.all, not one await after 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

Next.js
// 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();}
Next.js
// 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:

Angular
// 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}`);  }}
Angular
// 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: [] });}
Angular
// app.config.tsproviders: [provideHttpClient(withFetch()), provideClientHydration()];

Differences and gotchas

  • Next.js Server Components can query a database directly. Angular components always go through HttpClient to 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 REQUEST token from @angular/core (null in the browser and at build time).

Quick reference

Next.jsAngular
async Server Component + awaitService + toSignal() / rxResource
import 'server-only'Backend API (code never in the app)
cookies(), headers()REQUEST token
Data in HTML, no refetchHTTP transfer cache (provideClientHydration)