Skip to Content
All memories

Finance Terminal

 — #Next.js#TypeScript#App building

Building a Personal Finance Dashboard That Actually Respects Your Privacy

The frustration crept up on me gradually. I was opening a different app for every piece of my financial life. One for my bank. Another for investments. A third to track upcoming bills. Each app was showing me a different piece of the puzzle, and none of them talked to each other.

But the real problem was darker than the inconvenience. My financial data was scattered across half a dozen services, each one with their own business model. Some were selling my transaction patterns to data brokers. Others were packaging my spending habits for marketing firms. I had no way to know what they were really doing with this stuff because the privacy policies were written to be incomprehensible.

I decided I wanted something different. I wanted my data sitting somewhere I controlled. I wanted to be able to look at all my accounts and investments and spending patterns in one place, on my own infrastructure, without worrying that my financial life was being turned into a product.

What I Actually Built With

Building financial connectors from scratch is a nightmare that never ends. So I didn't. I used Plaid for banking and SnapTrade for investments because they already solved that part. The key difference was I wasn't going to let them be the end point. They'd just be the pipeline. My database would be where the data actually lives.

I picked Next.js and PostgreSQL because I already knew them. Not to be fancy, but because I know how they break and how to fix them when they do. The goal wasn't learning new tech, it was moving fast on the part that mattered: getting my data off other people's servers and onto my own.

The setup is simple. Plaid gives read-only access to bank transactions and account balances. SnapTrade does the same for investments. Both push data into a local PostgreSQL database that sits encrypted at rest. A Next.js frontend lets you actually see all of it in one place instead of jumping between apps.

Tech Stack

Layer Technology Why
Frontend React 19 + Next.js 16 I already know it, it's fast, it just works
Backend Prisma 7 + PostgreSQL 16 Type-safe queries, straightforward migrations
Auth NextAuth.js + GitHub OAuth Handles the boring parts of authentication
Banking Plaid Industry standard, read-only access, webhooks
Investments SnapTrade Multi-brokerage aggregation
Charts Recharts Clean visualizations without bloat
Security AES-256-GCM + JWT Encryption at rest, verified webhooks
Deployment Vercel Cron jobs built in, scales to zero cost

Learning How Professionals Handle Financial Data

Building this meant I had to actually understand financial security instead of just guessing. I dug into security whitepapers. Researched what encryption actually does at the algorithm level. Figured out what "read-only" access actually means when you're dealing with banking APIs. Three months of this taught me more about how financial systems work than the previous decade combined.

I ended up using AES-256-GCM to encrypt the access tokens sitting in the database. Plaid webhook updates get verified with JWT signatures to make sure they actually came from Plaid. Every piece of sensitive data is treated like it's the nuclear launch codes, because if you screw up financial security the consequences are real and they're yours to own.

Building Out Features

Early on it was just a dashboard showing net worth and the last few transactions. Nothing fancy. Then I started adding things based on how I actually use financial information instead of how personal finance apps think you should use it.

Core Features:

  • Dashboard - Real-time net worth, cashflow, and spending overview at a glance
  • Transactions - Searchable, filterable by date/category/account; exclude transfers to focus on actual spending
  • Spending Insights - Month-to-date and year-to-date category breakdowns showing where money actually goes
  • Pay Cycle Tracking - Know exactly how much you can safely spend before the next paycheck, with recurring expense accruals
  • Portfolio Management - All investments across multiple brokerages in one view with allocation weights and P&L
  • Supply Chain Intelligence - SEC filing data showing which companies are hiding in your portfolio and what risks they carry
  • Offline AI Assistant - Locally-running Ollama that answers questions about your financial data without sending it anywhere
  • Market Data - Real-time data from Yahoo Finance, FRED, Finnhub, and more
  • Settings & Rules - Configure paycheck patterns, recurring expenses, savings destinations, settlement rules

Live Dashboard

Here's what it actually looks like in action:

WYN Financial Dashboard - Overview view showing net worth, cashflow, recent transactions, and investment portfolio summary

The dashboard loads all your financial data in one view: net worth at the top left, income and spending trends, a 6-month cashflow chart, investment holdings, category spending breakdown, and recent transactions. Everything updates every 6 hours when the sync jobs run.


What's Actually Running

Frontend Architecture:

  • React 19 components on Next.js 16 App Router
  • React Aria for accessible form controls (works with keyboard and screen readers)
  • Recharts for clean, no-frills data visualizations
  • next-themes for dark/light mode toggling (dark mode first, obviously)
  • SCSS with colocated CSS Modules per component

Backend Architecture:

  • Prisma 7 ORM with @prisma/adapter-pg for direct database connections
  • PostgreSQL 16 with encrypted token storage (AES-256-GCM)
  • Structured data model: transactions, accounts, pay cycles, recurring expenses, savings destinations, settlement patterns
  • NextAuth.js 4 for GitHub OAuth with email allowlist

Data Pipeline:

  • Plaid syncs every 6 hours via Vercel cron jobs
  • SnapTrade syncs every 6 hours via Vercel cron jobs
  • Webhooks from Plaid for real-time transaction updates (ES256 JWT verified)
  • Careful transaction handling to prevent half-broken database states
  • Sync audit logs for debugging failures

Security Measures:

  • API endpoints require authentication (GitHub OAuth token or session)
  • Sync triggers require bearer token (CRON_SECRET)
  • Plaid webhooks verified with JWT signatures
  • Access tokens encrypted at rest with AES-256-GCM
  • SnapTrade user secrets encrypted in database
  • No sensitive data ever reaches the frontend unencrypted
  • Rate limiting on auth endpoints

The Performance Saga

The dashboard was snappy with six months of data. Then eight months. Then twelve. Then it wasn't snappy anymore. At some point I had to actually care about performance instead of just throwing it on the backlog.

The fix wasn't some fancy new technology. It was just better queries:

What Worked:

  • Stopped summing transactions on every dashboard load - cache those results and refresh on change
  • Materialized expensive calculations instead of computing them on the fly
  • Added strategic database indexes (way more than you'd initially think)
  • Implemented per-route query optimization
  • Hooked up Vercel Speed Insights to measure what's actually slow
  • Used Next.js streaming for Suspense boundaries to show partial content faster

The Numbers:

  • Dashboard load went from 2-3 seconds to under 500ms
  • Transactions page load dropped from 1.5s to 300ms
  • Cycle calculations went from computed-on-request to cached-and-refreshed
  • Added 7 targeted database indexes across the critical paths

Performance optimization is boring and unglamorous, but it's the difference between "kinda works" and "actually useful."

Before vs After

Aspect Before This Project After Building
Apps to Check 3-5 different apps 1 unified dashboard
Data Location Scattered across vendors Your own PostgreSQL
Data Privacy Used by third parties Encrypted, only you see it
Account View Manual checking each app Real-time aggregated view
Investment Tracking Multiple brokerages, no sync All positions in one view
Spending Analysis Maybe in one app Category drilldown across all banks
Paycheck Planning Guesswork Cycles with recurring expense tracking
Setup Complexity Sign up for each service One-time local setup
Update Frequency Manual Every 6 hours automated

Design and Styling

The visual inspiration came from Bloomberg Terminal and Linear. That combo sounds weird but it actually works:

  • Bloomberg Terminal: Dense, information-rich, everything organized and findable
  • Linear: Clean, purposeful, nothing gets in your way

Design System:

  • SCSS with design tokens for colors, spacing, typography, and component behaviors
  • CSS Modules colocated with React components (no global stylesheet mess)
  • Design tokens for consistent theming across light and dark modes
  • Responsive breakpoints for mobile (< 760px), tablet (761-960px), desktop (> 960px)
  • Dark mode built first (nighttime developer bias), light mode added for daytime users

Theme Support:

  • Full dark/light mode support via next-themes
  • Automatic theme detection based on system preference
  • Smooth transitions between modes
  • All charts, tables, and components support both themes
  • Dark mode makes sense for financial data dashboards (Bloomberg Terminal precedent)

Responsive Design:

  • Started desktop-first (oops)
  • Mobile-first redesign happened gradually as more features landed
  • Tablet-specific layouts for 7-10" screens
  • Table rows collapse to 2-line stacked layout on mobile
  • Navigation becomes hamburger menu on small screens
  • Touch-friendly interaction targets

Getting It Into Production

Hosted Deployment (Vercel)

Why Vercel:

  • Cron jobs configured in vercel.json (no additional job scheduler needed)
  • Database migrations run automatically during builds
  • Zero-downtime deployments
  • Built-in Speed Insights integration
  • Scales down to essentially free for low traffic

Production Setup:

  • GitHub connected for automatic deploys on push
  • Plaid sync runs every 6 hours (0:00 UTC)
  • SnapTrade sync runs every 6 hours (0:30 UTC)
  • Database: Managed PostgreSQL instance (not included, bring your own)
  • Environment variables loaded from Vercel secrets UI

Local Development

Quick Start:

npm install
cp .env.example .env
npm run secrets              # Generate encryption keys
npm run dev:db              # Start Docker PostgreSQL
npm run db:deploy           # Run migrations
npm run db:seed             # Seed demo data
npm run dev                 # Start Next.js dev server

Required API Keys (all free tier):

  • Plaid - Banking data (sandbox or production)
  • SnapTrade - Investment data
  • GitHub - OAuth authentication
  • TwelveData - Market data (optional)

Local Demo: Visit finance-demo.ionwyn.com to see the app in action without needing your own credentials. It's read-only and seeded with mock data, perfect for exploring the interface.

Where We Are Now

By The Numbers:

  • 428 commits across the development history
  • 400+ commits on the main branch
  • 9+ pages in the core application
  • 6 major feature areas (dashboard, transactions, spending, cycles, portfolio, supply chain)
  • 24/7 sync with Plaid and SnapTrade (every 6 hours, automated)
  • 2 theme modes (dark first, light for the day crew)
  • Mobile-responsive across all breakpoints

What It Does

Dashboard - Real-time net worth, cashflow trends, spending overview, investment summary
Transactions - Searchable, filterable by date/category/account, CSV export support
Spending Analysis - MTD/YTD category breakdowns with drill-down capability
Pay Cycles - Biweekly planning with recurring expense accruals and safe-to-sweep suggestions
Portfolio Tracking - Multi-brokerage view with allocation, performance, and P&L
Supply Chain Intelligence - SEC filing analysis showing portfolio risks
Market Data - Real-time prices, technical indicators, macro data
Offline AI - Local Ollama assistant for financial questions
Dark/Light Modes - Full theme support with automatic system detection
Mobile-Responsive - Works on phones, tablets, and desktops

The Philosophy

The core principle is simple: your financial data is yours.

  • It doesn't get sold to data brokers
  • It doesn't get run through algorithms to predict your behavior
  • It sits in your own database, encrypted at rest
  • Only you can see it
  • Everything is built on open standards
  • You can export everything and move it tomorrow if you want
  • You're never trapped by vendor lock-in

Why The Tech Choices Work

The stack is deliberately boring:

  • Next.js + React - Proven, stable, thousands of people use it
  • PostgreSQL - Rock solid, runs everywhere, doesn't need replacement
  • Plaid + SnapTrade - Industry standard integrations
  • TypeScript - Catches bugs before production

None of these are flashy or trendy. They all just work when you need them to. Performance is solid because it was treated as a real constraint from day one, not something to optimize later. Security matters because the consequences of messing it up are more than academic.


Key Lessons Learned

1. Use Tools You Already Know
Learning new tech is cool but slows you down. Next.js and PostgreSQL aren't exciting, but knowing them inside-out let me focus on the actual problem instead of fighting the framework.

2. Security is Not Negotiable for Financial Data
Once you're handling money, half-measures don't work. Encryption at rest, verified webhooks, rate limiting, and authentication aren't "nice to haves" anymore. They're table stakes.

3. Real Features Beat Flashy UI
A clean but boring interface that shows you what you actually need to know beats a pretty interface that requires three taps to get there. Bloomberg Terminal proved this decades ago.

4. Performance Matters from Day One
Optimizing performance after the fact is painful. It's way easier to avoid the bad query patterns from the start. That said, some optimization is inevitable as data grows.

5. Dark Mode First, Light Mode Second
If you're building financial software, dark mode should be the default. Your users are probably checking it at all hours, and burning their retinas isn't the vibe.

6. Your Boring Tech Stack Will Outlive Trendy Alternatives
TypeScript, PostgreSQL, React, and Next.js have been around for years and will be around for years more. Picking "boring but proven" is the opposite of a boring choice.


I built this because I was tired of opening different apps. I ended up learning more about how financial software actually works than I ever expected to. That part turned out better than planned.

I still use it every single day.