ArchitectureOverview

Architecture Overview

Guiding Principles

Patterns Over Frameworks

Every architectural decision is explained in terms of the underlying pattern first, then how React/Next.js expresses it. “Separation of concerns” is the pattern; “custom hooks for logic, components for UI” is the React expression.

Explicit Over Implicit

React has many “magic” conventions (file-based routing, automatic code splitting, hydration). The documentation makes these explicit - what happens, when, and why it matters.

One Way to Do Things

React offers many paths to the same outcome. This architecture documents ONE chosen pattern for each concern, with reasoning. No guessing.

Teachable and Transferable

Each pattern documented here should be applicable to other projects with minimal translation. The docs serve as a template, not just a reference.

Layer Responsibilities

LayerPurposeData Flow
pages/Routing, SSR decisionsReceives SSR data, passes to modules
modules/Feature implementationComposes shared components, uses hooks
shared/Reusable codeImported by modules
rendering/SSR/CSR patternsUsed by pages
layout/App shellWraps all pages

Data flows DOWN through layers. Pages compose modules. Modules compose shared. Never upward.

Dynamic Sections

The site structure is configuration-driven. A Section defines an area of the site with two key properties:

  • display_type: How content renders — feed (infinite scroll), card-grid (responsive grid), static-page (single block)
  • content_type: What items belong here — story, project, page

This separation enables combinations like blog feed (feed + story), project showcase (card-grid + project), or about page (static-page + page).

Registry Pattern

modules/registry/ maps display types and content types to React components:

  • Display registry: display_type → display component (FeedDisplay, CardGridDisplay, StaticPageDisplay)
  • Content registry: content_type → list item + detail components (StoryCard/StoryDetail, ProjectCard/ProjectDetail)

Adding a new type means creating components and registering them. No routing or page changes needed.

Dynamic Routing

A single catch-all route (pages/[...slugPath].tsx) resolves all content URLs via a resolve-path backend endpoint that handles any URL depth in one call. Sections support unlimited nesting through parent_id references and a materialized path field for O(1) URL resolution. When content or sections move, automatic 301 redirects preserve old URLs. See ADR-0007 for the full design rationale.

TopNav renders from database sections where nav_visibility = "main". A module-level cache in useNavSections prevents redundant API calls, with listener-based invalidation when sections are created, updated, or deleted.

Template System

Templates are CSS-only packages that control the visual identity of the site. Each template lives in templates/<name>/ and exposes a single index.css entry point imported in _app.tsx. No JavaScript, no React components — just CSS custom properties and component classes.

A template must provide:

  • tokens.css — CSS custom properties for colors, fonts, spacing, radii, and transitions, with dark mode overrides under .dark
  • typography.css — Font assignments and heading styles
  • layout.css — Layout dimension variables (--layout-nav-height, --layout-container-max-width, etc.)
  • components.css — Component classes (.card, .btn, .badge, .grid, .prose, etc.) consumed directly by React components

The full contract of required properties and classes is documented in templates/CONTRACT.md. Switching templates means copying the default, modifying the CSS, updating the import in _app.tsx, and adjusting site.config.json fonts if needed.

Site Configuration

site.config.json in the frontend root defines build-time site metadata loaded by the config/ module. It contains four sections:

KeyPurpose
siteTitle, tagline, author, copyright (supports {year} token)
fontsGoogle Fonts for heading and body text
navigationIcon map linking section slugs to icon names for mobile bottom nav
footerStatic footer links (Privacy, Terms)

The config loader reads this file once at build time and exposes typed accessors to components. Layout, TopNav, Footer, and _document.tsx (Google Fonts injection) all consume it.

How Templates and Config Interact

Templates define what the fonts look like (sizes, weights, line-heights via CSS custom properties). Site config defines which fonts to load (Google Fonts families injected in _document.tsx). The template’s --font-family-sans / --font-family-serif variables reference the families that site.config.json loads. Changing fonts requires updating both: the config to load the new family, and the template tokens to reference it.