Skip to content

Packages

What this answers: how is the code organised, and what are the dependency rules between packages?

Layout

src/
├── app/              # Next.js routes (pages + API)
├── components/       # UI components, organised by purpose
│   ├── ui/           # foundation (Button, Card, Badge, Container)
│   ├── domain/       # domain-specific (PriceTag, StockBadge, SocketChip, RetailerLogo)
│   ├── layout/       # TopNav, Footer
│   ├── landing/      # Hero, HeroBuildCard, PriceTicker, etc.
│   ├── build/        # BuildHeader, BuildSlotRow, CompatPanel, QuickStart
│   └── browse/       # CategoryNav, ProductRow
├── lib/              # build.ts, matching.ts, cn.ts, db.ts
├── rules/            # compat rule pure functions (psuSufficient, gpuFitsCase, ...)
├── scrapers/
│   ├── core/         # http.ts, parse.ts, normalize.ts (shared infra)
│   └── sites/        # one file per retailer
└── generated/prisma/ # Prisma client output (gitignored)

prisma/               # schema.prisma + migrations
scripts/              # one-shots (run-scrapers.ts, build-docs.sh)
tests/                # vitest specs, mirrored against src/

Dependency diagram

graph TD
    App[src/app]
    Components[src/components]
    Lib[src/lib]
    Rules[src/rules]
    Scrapers[src/scrapers]
    Prisma[prisma/]
    Tests[tests/]

    App --> Components
    App --> Lib
    Components --> Lib
    Lib --> Prisma
    Lib --> Rules
    Scrapers --> Lib
    Scrapers --> Prisma
    Tests --> Lib
    Tests --> Rules
    Tests --> Scrapers

Dependency rules

  1. Routes import from components and lib. Not the other way.
  2. Components import from lib (for utilities) but not from app. A component never knows what route it's rendered in.
  3. Lib is the lowest application layer. It depends only on Prisma generated types, npm packages, and src/rules/.
  4. Scrapers and app are siblings. Both consumers of src/lib. Scrapers never import from src/app (and vice versa).
  5. Rules are pure functions, no I/O. src/rules/*.ts files take inputs, return verdicts. They never read the DB or call APIs. Easy to test, easy to reason about.
  6. Prisma is generated, not hand-written. npx prisma generate rebuilds src/generated/prisma. Don't edit that directory.

Code smells we watch for

  • A component importing Prisma directly — should go via lib/ instead. Components are presentational; data access is lib's job.
  • A scraper importing from src/app — shouldn't happen. If a scraper needs route logic, that logic belongs in lib/.
  • A rule that calls the database — defeats the point. Move the data fetch to lib/, call the rule with the result.
  • Circular imports between lib/* modules — usually a sign that two modules share concerns and should merge or split.

Conventions

  • *.ts is server-side or universal. Client-only code is in *.tsx (with 'use client') or explicitly marked.
  • Tests mirror source. src/lib/matching.ts has its tests at tests/lib/matching.test.ts. Hard to lose track of what's tested.
  • No barrel exports. No src/lib/index.ts re-exporting everything. Imports are explicit; refactors are clearer.

This layout is small enough that monorepo tooling (turborepo, nx) is unnecessary. If it grows beyond ~20 modules per dir we'll revisit.