→ cards/01-server-action

Chapter

01 · Server Page (page.tsx)

Read-only orchestration: fetch via get*, enforce visibility, pass promises to UI.

Mental Model

  • Server Pages are read-only conductors; they assemble data, enforce visibility, hand off to UI.
  • Not smart, not mutable, not domain owners.
  • Use entity get* actions; prefer promise-passing for Suspense/streaming.

File Classification

Layer: Route entry / orchestration
Component: Server Component
Runtime: Server
Prisma: ❌ Never
Server Actions: ❌ (no mutations)
API Routes: ❌ Never
Suspense Prep: ✅ Yes

Should Do

  • Read data via entity actions (get*).
  • Coordinate multiple entities (read-only).
  • Enforce visibility/permissions (light gatekeeping).
  • Pass data or promises to client components; prepare Suspense.

Should Not (and where it belongs)

  • Prisma queries → put them in entity actions.
  • Mutations → server actions or API routes for external callers.
  • Business rules → domain/entity layer, not in page.tsx.
  • Internal API calls → call actions directly; skip fetch("/api/...").

Canonical Example

import { getProjects } from "./actions/getProjects";
import ProjectsClient from "./components/ProjectsClient";

export default async function ProjectsPage() {
const projectsPromise = getProjects(); // promise for Suspense
return <ProjectsClient projectsPromise={projectsPromise} />;
}

Colour-Coded Miniature

import { getProjects } from "./actions/getProjects";
import ProjectsClient from "./components/ProjectsClient";

export default async function ProjectsPage() {
const projectsPromise = getProjects(); // promise for Suspense
return <ProjectsClient projectsPromise={projectsPromise} />;
}

Quick Rules

  • Server Pages are read-only; no mutations.
  • Call get* actions only; never Prisma here.
  • Pass promises when possible (Suspense-friendly).
  • Gatekeep visibility/auth lightly; defer domain rules to entities.