Architecture
The whole system on one page. Every module in this course zooms into one region of this map.
A Next.js app talks directly to Postgres through store functions β no REST layer. Three background jobs (PDF compile, discovery cron, reminder cron) run on Trigger.dev and share the same stores. Five packages keep domain logic, data access, AI, connectors, and the LaTeX compiler behind clean boundaries. The database is the only shared state.
The system, clickable
Vercel β the request path
Trigger.dev β the background path
Shared packages
External services
The primary journey: paste a URL, get a tracked application
This is the flow the whole product exists to serve. Step through it:
The shape of the whole design
Wera is built like a well-run kitchen-table workshop with a locked toolshed. The workshop (the web app) does everything by hand, in the open, with tools it fetches from clearly labeled drawers (the packages). Anything dangerous β a stranger's LaTeX, a scraper's output, an AI's claims β never enters the workshop directly: it goes through an inspection bench first, where it's checked, stamped, and logged. And anything slow or scheduled happens in the shed out back (background jobs), so the workshop never blocks.
It's a modular monolith: one deployable app plus internal packages with one-way dependencies (app β packages, never the reverse), sharing a single Postgres instance as the only state. Reads bypass any API layer (server components call stores in-process); writes are server actions returning discriminated-union states. Background work is at-least-once by platform, made safe by idempotency keys with unique indexes at every side effect. Untrusted input crosses three hardened boundaries β zod schemas (connectors, AI), a subprocess sandbox (LaTeX), and an SSRF guard (user URLs).
Tradeoff: no service boundaries means no independent scaling and no fault isolation between features β accepted because the write volume is one human. The failure mode to watch is the Neon HTTP driver: every statement is a round-trip, so a 500-item ingest is ~2,500 sequential HTTP calls; the code compensates with caps and periodic progress flushes rather than batching.
Packages never import from the app, and core imports from nothing at all. When you can draw every arrow pointing the same direction, you can test each layer in isolation and replace any vendor at the seam. Engineers call this a dependency discipline.