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.