Overview
The higher-order thinking required to make durable technical decisions — framework selection, API contracts, Architecture Decision Records, and technical debt identification and prioritization.
Architecture & Decision Making
The most expensive engineering decisions are the ones that are hard to reverse. Framework choices, API contracts, and debt paydown strategy shape the entire codebase for years. This section covers the frameworks and tools for making those decisions deliberately — and for documenting them in a way that survives team turnover.
This section sits last intentionally. The patterns here only make sense with the context of everything that came before — rendering models, bundling trade-offs, delivery infrastructure — before framework and architecture decisions make sense.
What's Covered
Framework Selection — Multi-axis evaluation using a weighted scoring matrix. Criteria: rendering model fit, ecosystem maturity, team familiarity, deployment target, bundle size baseline, community health, migration cost, and lock-in risk. Runnable TypeScript scoring utility with adjustable weights. Heuristic decision tree: Astro for content-heavy sites (zero JS by default), Remix for web-standard SSR, Next.js for full-stack React on serverless/edge, SvelteKit for non-React teams prioritising bundle size. Lock-in assessment: pure React components and business logic are portable; routing (useRouter), server primitives (cookies(), headers()), and data loading patterns are framework-specific. Decision should always be documented in an ADR with the scoring, the rejected alternatives, and the context.
API Contract Design — Three contract approaches: tRPC (zero-codegen TypeScript contract — the router type flows from server to client; createCaller in Server Components, trpc.X.useQuery() in Client Components), shared Zod schemas (single source of truth for both runtime validation and TypeScript types; z.infer<> derives the type), OpenAPI with codegen (for public or polyglot APIs). Contract-first development: commit the schema before implementation — enables parallel frontend/backend work; when the backend changes totalAmount to totalAmountInCents, TypeScript flags every frontend callsite. Error envelopes: define { code: "NOT_FOUND" | "VALIDATION_ERROR" | ..., message, details? } as rigorously as success shapes. Runtime response validation: .parse() outgoing DB results — catches DB model drift during development rather than as production bugs. Breaking change strategy: rename-and-deprecate for internal APIs; version the endpoint (/v2/) for public APIs.
ADRs (Architecture Decision Records) — Format: title, date, status, deciders, context (what problem existed, what was evaluated), decision (the chosen option), consequences (positive AND negative trade-offs). Status state machine: Proposed → Accepted → Deprecated → Superseded; never edit an accepted ADR — write a new one that supersedes it. When to write: hard-to-reverse decisions (framework, DB, auth pattern), decisions not obvious from the code (why Kysely over Prisma), decisions reached after deliberation between alternatives. Never write ADRs for: naming conventions, minor library additions, obvious refactors. adr-tools automates sequential numbering and index generation. Store in docs/adr/ within the repo — in-repo ADRs appear in git log, get reviewed in PRs, and can't become stale the way Confluence pages do.
Technical Debt — Four types: deliberate (intentional shortcut with known owner), accidental (unaware of better pattern), bit rot (correct at the time but ecosystem moved on), architectural (structural problems that permeate the system). Prioritisation: impact (development slowdown, bug frequency, UX degradation) × (10 − effort) + compounding bonus. Compounding debt gets more expensive over time — an N+1 pattern in a shared component that every new feature copies scores higher than equivalent stable debt. Debt registry: machine-readable DebtItem[] with impact, effort, compounds, owner, issueUrl, createdAt — produces comparable priority scores. Making debt legible: weeklySlowdownHours × weeks × teamSize + bugFrequency × avgDebugHours converts debt to engineer-hours per quarter → dollar cost → tractable business decision for PMs. Sprint allocation: 20% rule — consistent fixed percentage each sprint prevents accumulation without pure debt sprints that kill morale. TypeScript strict: true + ESLint no-explicit-any prevent accidental debt at the source.
i18n Architecture
Structuring Next.js App Router for multiple locales — middleware locale detection, [locale] dynamic segment routing, server-side message loading, ICU plural rules, Intl API formatting, RTL logical CSS properties, missing key fallbacks, and namespace splitting for large catalogs.
Framework Selection
Evaluating frontend frameworks systematically — weighted scoring across rendering model, ecosystem maturity, team familiarity, deployment target, bundle size baseline, migration cost, and lock-in risk. Includes a runnable TypeScript scoring matrix and decision heuristics for Next.js, Remix, Astro, and SvelteKit.