Architecture
Tech stack, project structure, and data flow.
Tech Stack
- Framework — Next.js 14+ (App Router)
- Language — TypeScript
- Database — PostgreSQL via Prisma ORM (Neon serverless)
- Auth — NextAuth.js v4 with JWT sessions + optional MFA
- Payments — Stripe (subscriptions, embedded checkout)
- Banking — Plaid (account linking, transaction sync)
- Styling — Tailwind CSS with custom design tokens
- Charts — Recharts
- Email — Custom email templates via the email service
- Market Data — Yahoo Finance API (server-proxied)
Project Structure
app/ (auth)/ # Login, signup, MFA, password reset (dashboard)/ # Authenticated app pages admin/ # Admin panel (separate auth) knowledgebase/ # This knowledgebase (public) api/ # All API routes blog/ # Public blog components/ app/ # Feature-specific components ui/ # Reusable UI primitives lib/ # Shared utilities, services, configs prisma/ # Schema, migrations, seeds scripts/ # Dev/debug scripts
Data Flow
Authentication
NextAuth handles login/signup with JWT sessions. Middleware protects dashboard routes and checks MFA status. Admin uses a separate cookie-based token.
Plaid Integration
Link Token → Plaid Link UI → Exchange Token → Store access token → Cursor-based transaction sync. All Plaid calls are server-side. Cron jobs keep data fresh.
Tier Permissions
Every API route resolves the user's subscription tier via Stripe and checks feature access against a static tier config. Client-side gating shows upgrade prompts.
Subdomain Routing
Middleware detects subdomains (admin.*, knowledgebase.*) and rewrites to the appropriate route group. This allows separate layouts and auth for each context.
Key Patterns
- App Data Provider —
useAppData()hook provides cached user settings, tier status, and family data across all dashboard pages. - Error Handling —
withErrorHandlerwrapper on API routes for consistent error logging and responses. - Tier Config — Static feature config in
lib/tier-config.tsdefines what each tier can access. - View Density — User-selectable compact/comfortable/spacious layouts via context provider.
Security Model
Required Environment Variables
NEXTAUTH_SECRET— Signs user session JWTs. Must be >20 characters and not the placeholder string. Generate:openssl rand -base64 32ADMIN_PASSWORD_HASH— bcrypt hash of the admin password. Never set a plaintextADMIN_PASSWORD.ENCRYPTION_KEY— AES-256-GCM key for Plaid tokens at rest. Must be exactly 64 hex characters and cannot be all zeros. Generate:openssl rand -hex 32MFA_ENCRYPTION_KEY— AES-256-GCM key for MFA secrets at rest. Same format asENCRYPTION_KEY.
The server will throw at startup if NEXTAUTH_SECRET or ENCRYPTION_KEY are missing or use insecure placeholder values.
Auth & Session
- User sessions use NextAuth JWT strategy; MFA completion is tracked via an httpOnly HMAC-SHA256 signed cookie (not the raw user ID).
- Family child dashboard tokens are stored as SHA-256 hashes; raw tokens are shown only once at creation/rotation.
Input Validation
- All mutation endpoints validate with Zod schemas before touching the database.
- Budget creation enforces category ownership — category IDs from other users are rejected.
- File uploads are validated by binary magic bytes, not client-supplied MIME type.
- Profile image URLs are restricted to allowed CDN hostnames (
res.cloudinary.com). - Invoice PUT is schema-validated — only editable fields (dates, items, notes) are accepted; system fields like
userId,status, andcustomerTokencannot be overwritten.
Rate Limiting
Sensitive endpoints are rate-limited per IP. On Vercel the x-real-ip header (set by Vercel's infrastructure and not spoofable by clients) is authoritative; x-forwarded-for is used as a fallback for other reverse proxies.
The unauthenticated error-report endpoint is also rate-limited (10 req/min/IP) to prevent database abuse.
Note: The current in-memory limiter is shared only within a single serverless function instance. For stricter enforcement across concurrent instances, swap the store in lib/rate-limit.ts for an Upstash Redis counter.