Most SaaS founders track their metrics in spreadsheets. I did too — until I missed a churn spike and lost 30% of my MRR in one month. That's when I built AI Finance Ops Copilot, an AI-powered financial dashboard that connects to Stripe and automates everything founders hate doing manually.
Here's how I built it, what I learned, and why the tech stack matters.
The Problem
I was running a bootstrapped SaaS and tracking MRR, churn, and runway in Google Sheets. The problems:
- Manual data entry — copying Stripe data every week
- No forecasting — I couldn't predict cash flow
- Reactive, not proactive — I found out about problems after they happened
- Spreadsheet errors — one broken formula = wrong decisions
I looked at tools like Baremetrics ($308/mo) and ChartMogul ($59/mo). Too expensive for a solo founder.
So I built my own.
Tech Stack
Frontend: Next.js 16 (App Router)
Styling: Tailwind CSS
Database: Supabase (PostgreSQL)
Auth: Supabase Auth
Payments: Stripe + LemonSqueezy
AI: OpenAI GPT-4 for insights
Hosting: Vercel
Why this stack?
Next.js 16 — Server-side rendering for the marketing site, API routes for Stripe webhooks, and App Router for clean file-based routing. The new use hook and server components made data fetching trivial.
Supabase — Postgres database with real-time subscriptions, auth, and row-level security. Free tier is generous enough for MVP.
Stripe Connect — Users connect their Stripe account via OAuth. I pull subscription data, invoices, and payment events in real-time.
Architecture
┌─────────────────┐ ┌──────────────┐ ┌─────────────┐
│ Marketing Site │ │ Dashboard │ │ Stripe │
│ (Next.js SSR) │ │ (Client) │ │ Webhooks │
└────────┬────────┘ └──────┬───────┘ └──────┬──────┘
│ │ │
└─────────┬───────────┘ │
│ │
┌────▼────┐ ┌──────▼──────┐
│ Supabase│◄────────────────────│ Webhook │
│ DB │ │ Processor │
└─────────┘ └─────────────┘
The key insight: **process Stripe webhooks server-side**, not client-side. This means:
1. Real-time data (no polling)
2. Reliable (webhooks retry on failure)
3. Secure (Stripe signs every webhook)
## Building the MRR Calculator
The core feature is MRR tracking. Here's the simplified logic:
typescript
// src/lib/mrr.ts
export function calculateMRR(subscriptions: Subscription[]): number {
return subscriptions.reduce((total, sub) => {
const monthlyAmount = sub.recurring.amount /
(sub.recurring.interval === 'year' ? 12 : 1);
return total + monthlyAmount;
}, 0);
}
But MRR alone isn't enough. Founders need:
- **MRR breakdown** (new, expansion, churned, reactivation)
- **Net MRR** (new + expansion - churn)
- **MRR movement** (month-over-month change)
Adding AI Insights
The "AI" part isn't just a buzzword. I use GPT-4 to analyze metrics and generate actionable insights:
typescript
async function generateInsights(metrics: Metrics) {
const prompt = `
Analyze these SaaS metrics and provide 3 actionable insights:
MRR: $${metrics.mrr}
Churn Rate: ${metrics.churnRate}%
LTV: $${metrics.ltv}
CAC: $${metrics.cac}
Focus on: revenue growth, churn reduction, and cash flow.
`;
const response = await openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: prompt }],
});
return response.choices[0].message.content;
}
Real examples of AI insights the dashboard generates:
- "Your churn rate increased 12% this month. Consider reaching out to users on annual plans expiring in 60 days."
- "LTV/CAC ratio is 3.2x — healthy. But CAC increased 8% while LTV stayed flat. Monitor ad spend."
- "Runway is 14 months at current burn rate. If you reduce Churn by 0.5%, runway extends to 18 months."
The Hard Part: Real-time Calculations
The hardest technical challenge was calculating metrics in real-time as Stripe webhooks arrive.
**The problem:** MRR depends on all active subscriptions. When one subscription changes, you need to recalculate everything.
**Solution:** Event sourcing with materialized views.
sql
-- Store every subscription change
CREATE TABLE subscription_events (
id UUID PRIMARY KEY,
stripe_subscription_id TEXT,
event_type TEXT, -- 'created', 'updated', 'deleted'
amount INTEGER,
interval TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Materialized view for current MRR
CREATE MATERIALIZED VIEW current_mrr AS
SELECT
SUM(amount / CASE WHEN interval = 'year' THEN 12 ELSE 1 END) as mrr
FROM subscription_events
WHERE status = 'active';
On each webhook, I refresh the materialized view. PostgreSQL handles the heavy lifting.
Deployment on Vercel
Vercel makes deployment trivial:
bash
git push origin main
Auto-deploys to production
But there's a catch with Next.js 16 and large sites. My sitemap has 76+ pages. Build times were 3-4 minutes.
**Fix:** Use `export const dynamic = 'force-static'` on marketing pages and incremental static regeneration (ISR) for blog posts.
typescript
// app/blog/[slug]/page.tsx
export const revalidate = 3600; // Revalidate every hour
Results
After 3 months:
- **46 blog posts** targeting SaaS finance keywords
- **8 interactive calculators** (MRR, churn, LTV, runway, etc.)
- **100+ organic visitors/month** (growing 20% MoM)
- **Performance score: 100** on Lighthouse
The calculators are the biggest traffic drivers. Founders Google "MRR calculator" and land on my tool.
What I'd Do Differently
1. **Start with the database schema** — I redesigned it 3 times
2. **Webhook-first architecture** — Don't poll Stripe API
3. **Blog from day one** — Each post is a long-term traffic asset
4. **Free tier first** — Let users experience value before paying
Try It
The dashboard is live at aifinanceops.app. Free tier includes:
- MRR tracking
- Churn analysis
- Basic forecasting
- 3 AI insights/day
Paid plans ($29-$79/mo) add unlimited AI insights, custom reports, and team access.
---
*Built with Next.js 16, Supabase, Stripe, and OpenAI. Open to feedback and contributions.
Top comments (0)