Files
gridpilot.gg/docs/architecture/website/WEBSITE_RSC_PRESENTATION.md
2026-01-12 19:24:59 +01:00

2.8 KiB

Website RSC Presentation Architecture (Strict)

This document defines the only allowed presentation architecture for apps/website (Next.js App Router).

It is website-only and does not change apps/api or core architecture.

Authoritative contract: WEBSITE_CONTRACT.md.

1) Core rule: API owns business truth

  • apps/api is the only source of truth for business rules and canonical filtering/sorting.
  • apps/website is presentation infrastructure: composition, routing, caching, and rendering.

2) The three website presentation data types

2.1 Page DTO

Purpose: server-to-client payload.

Rules:

  • JSON-serializable only.
  • Contains raw values only (ISO date strings, numbers, codes).
  • MUST NOT contain class instances.

2.2 ViewModel

Purpose: client-only presentation model.

Rules:

  • Class-based.
  • Instantiated only in 'use client' modules.
  • Composes Display Objects.
  • NEVER passed into Templates.

2.3 ViewData

Purpose: Template input.

Rules:

  • JSON-serializable only.
  • Contains only values ready to render (mostly strings/numbers).
  • Built from API DTO directly in RSC.

The mapping between API DTO and ViewData is performed by ViewData Builders. See BUILDERS.md.

3) Required per-route structure

Server Components (RSC)

Every RSC route MUST follow:

  1. page.tsx: calls a PageQuery
  2. page.tsx: builds ViewData using ViewDataBuilder
  3. page.tsx: renders Template with ViewData

Example:

export default async function AdminDashboardPage() {
  const apiDto = await AdminDashboardPageQuery.execute();
  const viewData = AdminDashboardViewDataBuilder.build(apiDto);
  return <AdminDashboardTemplate viewData={viewData} />;
}

Client Components

Client components that need API data MUST follow:

  1. *Client.tsx: fetches API DTO
  2. *Client.tsx: builds ViewModel using ViewModelBuilder
  3. *Client.tsx: renders Template with ViewModel

Example:

'use client';

export function AdminDashboardClient() {
  const [viewModel, setViewModel] = useState<AdminDashboardViewModel | null>(null);
  
  useEffect(() => {
    const apiDto = await adminApiClient.getDashboard();
    const vm = AdminDashboardViewModelBuilder.build(apiDto);
    setViewModel(vm);
  }, []);
  
  return viewModel ? <AdminDashboardTemplate viewModel={viewModel} /> : null;
}

All writes enter through Server Actions. See FORM_SUBMISSION.md.

4) Authoritative specification

This document is an entry point only.

The authoritative, test-enforced spec lives at:

Final contract: