Case Study
Case Study: Building Focus: Finance
By Spencer Hill, Founder · Published May 24, 2026
Last updated: May 24, 2026
The problem
Focus: Finance is our in-house attempt to fix a problem we kept hitting personally and watching clients hit: there is no single surface that gives a serious person an honest, real-time view of their financial life. Mint shut down. The replacements are either aggregator dashboards with stale balances and noisy charts, or spreadsheets that demand an hour of weekend bookkeeping. Neither actually reasons about your money — they just show it back to you.
We wanted three things in one product. First, a personal financial command center: net worth, cash flow, holdings, debts, and upcoming obligations on a single screen, with the data fresh enough that you would trust a decision made from it. Second, a real-time portfolio view — not last-night-close, but live during market hours, with the same latency budget as a brokerage app. Third, AI-assisted analysis: ask the dashboard what changed since last month, why your burn rate shifted, or whether a tax-loss harvesting move makes sense given your current positions, and get a grounded answer in seconds.
Constraints
- Solo team. One engineer, one designer-hat-wearing engineer, same person. Every architectural decision had to be defendable on a Sunday afternoon.
- Ship in seven weeks. We gave ourselves a hard internal deadline to force trade-offs. Anything that did not get us to a usable dashboard in week seven got pushed.
- Real-time data. Positions and quotes had to refresh on a polling cadence fast enough to feel live during market hours, without melting our API budget.
- AI-first UX. The model is not a sidebar feature. The first answer to any question should come from the AI layer, with the raw data available underneath for verification.
Stack
- Next.js 15 with the App Router and React Server Components.
- TypeScript end-to-end, strict mode, no
anyin committed code. - Cloudflare Pages for hosting, with selected routes on the edge runtime.
- Anthropic API (Claude) for all analytical and conversational features.
- Polygon.io for real-time market data — quotes, fundamentals, and aggregates.
- Tailwind CSS for styling, with a small set of hand-rolled primitives.
Architecture
The dashboard shell is a server component tree. The outermost layout fetches the user's account graph — accounts, holdings, obligations — at the edge and hands a fully hydrated snapshot down to client components for interactive views like the live portfolio ticker and the AI chat surface. We deliberately kept the shell static-first: most of the page is rendered HTML on first paint, and only the cells that actually need motion become client components.
Hot routes — quote lookups, intraday aggregates, the AI streaming endpoint — all run on Cloudflare's edge runtime. That gives us a cold-start budget under 50 ms in practice, which matters when a user clicks a ticker and expects a chart immediately. Long-running or memory-heavy work (PDF statement parsing, historical backfills) runs on the Node runtime and is invoked through a queue.
The AI sits in two places. First, as an “explainer” layer on the dashboard itself: every card has a small prompt that generates a one-sentence interpretation of the underlying numbers (“your cash position is up 3.2% week-over-week, driven mostly by a paycheck deposit on Friday”). Second, as a full conversational surface where the user can ask arbitrary questions and the model is given tool access to the account graph and a read-only SQL view over the user's data.
Key decisions and trade-offs
- Server components for the dashboard shell. We considered an SPA with a single client-side data fetcher. The problem is that a finance dashboard has at least a dozen distinct data dependencies, and a client-side fan-out either waterfalls or forces an awkward central store. RSC let us co-locate data and UI, ship less JavaScript, and keep the first contentful paint under half a second on slow connections.
- Edge runtime for hot routes. We tested both runtimes on the same quote endpoint. Node averaged 180 ms p50 with a long tail; edge averaged 38 ms p50 with a much tighter distribution. For routes that hit external APIs the picture is muddier, but for our own KV-backed lookups the edge won decisively.
- Anthropic over OpenAI for analysis. We ran the same dashboard-explainer prompt against four models from two providers. Claude consistently produced shorter, less hedged, less hallucinated explanations on numerical inputs, and its tool-use loop was easier to constrain. We also liked the economics: median completion for a 200-token analysis lands at roughly 1.8 s and a fraction of a cent.
- No auth provider in v1. We shipped with a single-tenant deployment per user. That sounds extreme but it removed an entire category of work — RLS, multi-tenant data isolation, auth flows — and let us focus on the product. It is the kind of trade-off that only makes sense when you know the second version will replace it; we have the schema for the multi-tenant migration already written.
- Polling, not WebSockets, for live quotes. A WebSocket pipeline would be lower latency but adds a stateful service, reconnect logic, and a different deployment shape. A two-second polling interval on the edge, batched per visible ticker, gave us the “live” feel without the operational tax.
- Prompts as code, not as config. Every prompt lives in a TypeScript module with typed inputs and outputs, version-pinned, and is covered by snapshot tests. We treat prompt regressions the same way we treat code regressions.
What worked
- Performance. Cold edge response under 50 ms. Dashboard p50 first paint at 480 ms cold, 110 ms warm. The whole app weighs about 92 KB of JavaScript gzipped on the initial route.
- AI latency. Median Claude completion for a 200-token analysis at 1.8 s; streaming first token typically inside 600 ms. That is fast enough that the model feels like part of the UI, not a separate request.
- Cost. All-in cost per active session — compute, market data, model calls — has been under $0.04. That gives us a comfortable margin if and when we commercialize.
- Velocity. Seven weeks from empty repo to a v1 that we actually use daily. The biggest single contributor was refusing to introduce a database abstraction layer until we had three concrete reasons to.
What we'd do differently
- Wire observability from day one. We added structured logs and traces in week five. We should have done it in week one — most of our debugging time in weeks three and four would have evaporated.
- Pin model versions earlier. We left the model identifier as a floating alias for the first month and spent an afternoon chasing a prompt regression that was actually a silent model upgrade.
- Build the import pipeline first. Manual data entry for the first two weeks was a tax on dogfooding; once we had statement import working, our own usage doubled.
- Resist the urge to generalize. Two of the abstractions we built in week four got deleted in week six because the second use case never materialized.
Lessons applicable to client work
Most of what we learned building Focus: Finance translates directly into the engagements we run for clients. The big one: AI features are a UX problem before they are a model problem. The interesting decisions are about where the model lives in the product, what it can see, and how its output is grounded — not which model you call. The second: edge-first deployment on Cloudflare changes the cost structure of an app like this enough that it is worth designing for it from day one rather than retrofitting later. The third: typed prompts and prompt regression tests are not optional once you have more than a handful of model calls in production.
If you are building something in this shape — an AI-first product on a tight timeline with a small team — we can help. Read more about how we approach AI integration and web development, or get in touch and we will talk about your project.
Last updated: May 24, 2026