Cashflow Simulator: Next.js Deep Dive
What Is It?
The Cashflow Simulator is a full-stack financial planning tool built for Thai Baht (฿) markets. It lets users model income streams, expense categories, and investment scenarios in real time — no backend, no database, no auth.
Everything runs in the browser. State lives in React. Calculations are pure functions. The only “server” is Vercel’s edge network serving static assets.
Why Next.js?
For this project, Next.js made sense over Astro because:
- Dynamic interactivity — The simulator is a rich client-side app with complex state. Astro’s islands model would have meant hydrating the entire thing anyway.
- App Router familiarity — The file-based routing and layout system fit naturally for a multi-view financial tool.
- React ecosystem — Access to charting libraries, form validation, and financial math utilities.
Architecture
The core data model is a CashflowScenario object:
interface CashflowScenario {
income: IncomeStream[];
expenses: ExpenseCategory[];
investments: InvestmentEntry[];
period: 'monthly' | 'quarterly' | 'yearly';
currency: 'THB' | 'USD';
}
All calculations are pure functions that take a scenario and return derived values — net cashflow, savings rate, projected balance. No side effects, fully testable.
Thai Baht Support
Localizing for Thai Baht meant:
- Number formatting —
Intl.NumberFormat('th-TH', { style: 'currency', currency: 'THB' }) - Exchange rate display — Static USD/THB rate with a clear “approximate” disclaimer
- Large number handling — Thai financial convention uses different decimal scales
The currency system is pluggable — adding new currencies is a one-line change to the config.
Real-Time Calculations
React’s useMemo does the heavy lifting. Every derived value is memoized based on the scenario state. Updates to any input field trigger only the affected calculations, not a full recompute.
For the chart data, I use a rolling window algorithm that projects 12 months of cashflow given current inputs. The projection updates instantly as the user types — no debouncing needed at these calculation sizes.
Deployment
Vercel’s zero-config deployment made this trivial. Push to main, preview URL appears, merge to deploy to production. The entire CI/CD pipeline is git push.
The one gotcha: Next.js 16’s app router requires Node 18+. Vercel handles this automatically, but local dev needed a version bump.
Lessons Learned
- Pure functions first — Keeping calculations stateless made debugging trivial. When a number was wrong, the function was wrong — no action to trace through.
- TypeScript strict mode — Caught three classes of bugs before they hit the browser: nullable fields, wrong unit assumptions, missing currency conversions.
- Don’t over-engineer — I started with Zustand for state management, then ripped it out.
useState+useMemowas sufficient and simpler.
The simulator is open source. View the code on GitHub.