Files
gridpilot.gg/docs/architecture/website/VIEW_DATA.md
2026-01-24 01:22:43 +01:00

3.7 KiB

ViewData (Website Templates)

ViewData is the only allowed input type for Templates in apps/website.

Authoritative contract: WEBSITE_CONTRACT.md.

1) Definition

ViewData is a JSON-serializable, template-ready data structure:

  • primitives (strings/numbers/booleans)
  • arrays and plain objects
  • null for missing values

Uncle Bob says: "Data structures should not have behavior." ViewData is a dumb container.

2) What ViewData is NOT

ViewData is not:

  • an API Transport DTO (raw transport)
  • a ViewModel (client-only class)
  • a Display Object instance (rich API)

3) Construction rules

ViewData is created by ViewData Builders:

Server Components (RSC)

const apiDto = await PageQuery.execute();
const viewData = ViewDataBuilder.build(apiDto);
return <Template viewData={viewData} />;

Client Components

'use client';

const [viewModel, setViewModel] = useState<ViewModel | null>(null);

useEffect(() => {
  const apiDto = await apiClient.get();
  const viewData = ViewDataBuilder.build(apiDto);
  const vm = ViewModelBuilder.build(viewData);
  setViewModel(vm);
}, []);

// Template receives ViewData directly
return viewModel ? <Template viewData={viewData} /> : null;

Templates MUST NOT compute derived values.

ViewData Builders MUST NOT call the API.

Important: ViewModels are built from ViewData, not directly from DTOs. This ensures ViewModels are decoupled from the API transport layer.

4) Formatting and SEO

ViewData is responsible for providing fully formatted strings to Templates for Server-Side Rendering (SSR).

  • SEO Requirement: All data required for search engines (prices, dates, counts, labels) MUST be formatted in the ViewData Builder on the server using Formatters.
  • Template Simplicity: Templates should simply render the strings provided in ViewData without further processing.

5) Determinism rules

Any formatting used to produce ViewData MUST be deterministic.

Forbidden anywhere in formatting code paths:

  • Intl.*
  • Date.toLocaleString() / Date.toLocaleDateString() / Date.toLocaleTimeString()

Reason: SSR and browser outputs can differ.

Localization MUST NOT depend on runtime locale APIs. If localized strings are required, they MUST be provided as deterministic inputs (for example via API-provided labels or a deterministic code-to-label map) and passed through ViewData Builders into ViewData.

6) Relationship to ViewModels

ViewData serves as the stable, serializable contract between the server and client. It is:

  • The input for Templates (both SSR and Client)
  • The input for ViewModelBuilders (Client-side state initialization)

ViewModels are built from ViewData, not from DTOs. This ensures:

  • ViewModels remain decoupled from API transport concerns
  • ViewModels can be initialized from any source that provides ViewData
  • The ViewModel layer is purely for client-side interactive state

Important: ViewData MUST NOT contain ViewModel instances. ViewModels are classes with logic; ViewData is plain JSON.

7) Relationship to Formatters & Display Objects

Formatters are used to implement formatting/mapping, but their instances MUST NOT be stored inside ViewData.

Only primitive outputs produced by Formatters may appear in ViewData.

The "Redundancy" Question

DTOs, ViewData, and ViewModels might share similar fields, but they are NOT redundant:

  1. DTO: Raw API contract (Backend owned).
  2. ViewData: Page-specific "bag of data" (Server-to-Client transport).
  3. ViewModel: Interactive UI logic and computed properties (Client owned).

Using all three ensures that a change in the API doesn't break the Template, and a change in UI logic doesn't require a server-side deployment.