How the codebase is organized
Route groups#
The app splits into two route groups under app/:
(auth) — unauthenticated pages: login, register, forgot-password, reset-password, email-verified.
(main) — everything behind sign-in: missions, merchants, items, reports, profile. Each of the first four follows the same shape — a list page (page.tsx), a [id] dynamic route for the detail view, and a components/ folder for page-specific UI, matching the project's own rule that "feature-specific components belong within a /component subdirectory of their parent feature."
missions additionally has its own items/ subroute for managing the squad's shared MissionItem list independent of any single trip.
Server actions#
Mutations live in actions/ as one file per domain — auth.ts, members.ts, merchants.ts, missions.ts, receipts.ts — rather than API routes. This keeps write logic co-located by feature instead of scattered across a REST-style API surface.
Shared logic#
lib/ holds the cross-cutting pieces: auth.ts and auth-client.ts (better-auth server/client setup), prisma.ts (the database client), email.ts (Resend integration), realtime.ts (squad live-sync), scan.ts (the scan-service client), receipt-processing.ts, and mission-item-ordering.ts (the aisle-sort logic behind Aisle Rules).
The scan service is a separate system#
Receipt scanning isn't implemented inside the Next.js app — NEXT_SCAN_SERVICE_URL points at an external service that decodes NFC-e QR codes, and Conduit authenticates its requests to it with a signed JWT (SCAN_JWT_PRIVATE_KEY). The service calls back into Conduit through a webhook secured by RECEIPT_WEBHOOK_SECRET. This boundary is why manual report entry works even in environments where the scan service isn't deployed — see Environment Variables.
PWA and error reporting#
Conduit registers a service worker (app/sw.ts, via Serwist) and ships a web app manifest, so it installs as a standalone, portrait-oriented app on a phone — sensible for something you'll have open while walking around a store. instrumentation.ts wires @sentry/nextjs to capture errors, and specifically attaches the signed-in user's ID and email (read from the better-auth session cookie) before reporting, so errors in production are traceable back to an account without you having to ask who hit it.
Related: Data Model for what these routes and actions read and write, Tech Stack for the underlying libraries, Deploying with Docker for how this all ships, or back to the documentation overview.