Skip to content

Architecture

Emmanuel Knafo edited this page Jun 18, 2026 · 1 revision

Architecture

The site is a Next.js App Router static export: every page is pre-rendered to HTML at build time and served as plain files. There is no server runtime, no database, no API, and no live podcast feed. All content is embedded in source-controlled TypeScript modules.

This architecture is a direct consequence of the Project Constitution, whose first principle is Static Output First.

Technology choices

Concern Choice Notes
Framework Next.js App Router output: "export", trailingSlash: true, images: { unoptimized: true }
Language TypeScript (strict) Path alias @/* to ./src/*
UI React Server components by default; client components only where interactivity is needed
Styling Plain CSS with custom properties Design tokens in src/styles/globals.css; no CSS framework
Content Typed TS modules src/content/podcast.ts, episodes.ts, faq.ts
Linting ESLint 9 flat config Extends next/core-web-vitals and next/typescript
Testing Playwright + @axe-core/playwright Content, accessibility, and responsive suites

Routes

The site has exactly four primary routes, each pre-rendered to its own index.html.

flowchart LR
    L[/ Landing\nhero + 1 featured episode/] --> E[/episodes\n20-episode catalog/]
    L --> A[/about\nshow premise + host/]
    L --> F[/faq\n7 Q&A items/]
    E --> L
    A --> L
    F --> L
Loading
Route File Content
/ app/page.tsx Hero identity and the single featured episode
/episodes app/episodes/page.tsx All 20 mocked episodes, newest first
/about app/about/page.tsx Premise, host, audience, and tone
/faq app/faq/page.tsx Seven question-and-answer items

Project layout

app/
├── layout.tsx          # html shell, skip link, header/footer, metadata
├── page.tsx            # landing page
├── episodes/page.tsx
├── about/page.tsx
└── faq/page.tsx

src/
├── components/
│   ├── SiteHeader.tsx          # nav with aria-current
│   ├── SiteFooter.tsx
│   ├── MockEpisodeAction.tsx   # in-page mock action (client component)
│   ├── EpisodeMeta.tsx         # date/runtime/guest/category display
│   ├── FeaturedEpisode.tsx
│   ├── EpisodeCard.tsx
│   ├── EpisodeCatalog.tsx
│   └── FaqList.tsx
├── content/
│   ├── podcast.ts      # PodcastProfile (name, premise, host, about sections)
│   ├── episodes.ts     # exactly 20 Episode records + helpers
│   └── faq.ts          # FaqItem records
└── styles/
    └── globals.css     # tokens, base, responsive design system

public/assets/
└── signal-static-artwork.svg

tests/
├── build/content-contract.spec.ts        # data invariants
├── accessibility/primary-pages.spec.ts   # axe scans
└── e2e/responsive-navigation.spec.ts     # viewports + navigation

Content model

Content is typed and validated at the source. The key entities mirror the specification's data model.

  • PodcastProfile — name, tagline, premise, audience, tone, host name, and the about-page sections.
  • Episode — id, slug, number, title, summary, description, ISO publish date, duration, category, optional guest, featured flag, and the mock action label and message.
  • FaqItem — id, question, answer, optional category.

episodes.ts exposes helpers that enforce the invariants from the spec:

export function getFeaturedEpisode(): Episode {
  const featured = episodes.filter((e) => e.featured);
  // throws unless exactly one episode is featured
  ...
}

This is where intent becomes code: the spec requires exactly one featured episode within the catalog of twenty, and the helper makes any violation a hard failure rather than a silent bug.

Why static export

  • Operationally trivial — host the out/ directory on any static host or CDN. No servers to patch, scale, or page someone about at 3 a.m.
  • Deterministic and cheap — no request-time rendering, no runtime data fetches, minimal bundle.
  • Secure by construction — no server code path, no secrets in the client bundle, no live external dependency to fail.
  • Accessible and fast — pre-rendered semantic HTML with a small CSS layer.

Rendering and interactivity

Most components are React server components rendered to static HTML. Only the pieces that need browser interactivity are client components:

  • SiteHeader reads the current path to set aria-current on the active link.
  • MockEpisodeAction toggles an in-page status notice when a mock episode action is activated — no navigation, no dead link, no real feed.

Continue to Testing and Validation to see how these guarantees are enforced automatically.

Clone this wiki locally