ADR-008: Modular, Registry-Driven Architecture for Content Details Page

Prev Next

Context

The existing Content Details page had grown into a single, monolithic React component exceeding ~2000 lines.
It handled:

  • UI layout
  • content-type branching
  • data fetching and autosave logic
  • rendering of dozens of conditionally loaded sections
  • content-type–specific business rules

This resulted in:

  • Extremely high cognitive load when making changes
  • Frequent regressions when adding or modifying content types
  • Widespread if (contentType === …) checks scattered across the page
  • Tight coupling between UI, content taxonomy, and backend GraphQL enums
  • Poor extensibility and near-zero local reasoning (small changes required global understanding)

A decision was needed to stop incremental patching and instead redesign the Content Details page in a way that:

  • scales with new content types and sections
  • avoids conditional rendering logic in the page itself
  • allows progressive migration without breaking production
  • clearly separates “what is shown” from “how it is rendered”

Decision

The Content Details page will be re-architected as a modular, registry-driven detail page with the following principles:

  1. Section-based architecture

    • The page is composed of independent, self-contained sections (e.g. basic-details, images, monetization).
    • Each section owns its own UI and form logic.
    • Sections never check contentType and never import other sections.
  2. Central Section Registry

    • A single ContentDetailSectionsRegistry maps section keys to dynamically loaded section components.
    • Section keys are derived directly from the registry (keyof typeof Registry) to ensure type safety and alignment.
    • All sections are loaded via next/dynamic with ssr: false.
  3. Declarative Tabs Configuration

    • A ContentDetailTabsConfig defines, per content type, the ordered list of sections to render.
    • Tabs config uses section keys from the registry and contains no rendering logic.
    • Content type → sections mapping is explicit and readable.
  4. Thin Orchestrator Page

    • The main Content Details page is responsible only for:

      • loading the correct tab config
      • rendering the active tab
      • providing shared page context
    • No content-type branching or section-specific logic exists in the page.

  5. react-hook-form for Form State

    • Formik is replaced with react-hook-form to better support dynamic mounting/unmounting of sections and improve performance.
    • Sections register their own fields via shared form context.

This design intentionally decouples UI composition from backend transport concerns (e.g. GraphQL enums).

Image

Consequences

Enables

  • Adding a new section without modifying existing sections or the main page
  • Adding or reordering sections for a content type via configuration only
  • Lazy-loading heavy sections for improved performance
  • Safer, incremental migration from the legacy page
  • Local reasoning: most changes are confined to a single section folder

Restricts

  • Sections cannot directly depend on other sections
  • Content-type–specific behavior must be expressed via configuration, not conditionals
  • Some duplication of small UI patterns across sections is accepted to preserve isolation

Trade-offs

  • Slightly higher upfront structural complexity
  • Requires discipline to prevent reintroducing conditional logic
  • Page logic is no longer colocated in a single file, requiring navigation across modules

These trade-offs were accepted to significantly reduce long-term maintenance cost and regression risk.

Notes

  • This ADR establishes the architectural foundation; individual sections will be migrated incrementally.
  • Content type keys are intentionally not hard-coupled to GraphQL enums, as backend support varies.
  • Future ADRs may extend this pattern to other large modules/pages.
  • A key invariant to preserve:
    If a section needs to know the content type, the architecture is being violated.