Claude Code Boilerplate
FeaturesPricingBlogDocs
Get started →

Product

  • Features
  • Pricing
  • Skills

Compare

  • vs ShipFast
  • vs MakerKit
  • vs supastarter

Resources

  • Docs
  • Blog
  • Discord

Legal

  • License
  • Privacy Policy
  • Terms of Service
Claude Code Boilerplate

© 2026 Claude Code Boilerplate. All rights reserved.

← All posts

How to Build a SaaS Metrics Dashboard With Next.js -- Track MRR, Churn, and LTV Without Baremetrics

July 30, 2026
nextjsstripedrizzle-ormsaasrecharts

Most SaaS founders find out their monthly recurring revenue from a Stripe dashboard that was not designed for decision-making. You see a total. You do not know if this month is better or worse than last, how many customers you lost, or what the average customer is worth over a lifetime. Tools like Baremetrics and ChartMogul solve this, but they charge $150 a month before you have proven your product.

If you are already using Stripe subscriptions in your Next.js app, you have the data. It just needs to be surfaced.

The three numbers that actually matter

Before building anything, decide which metrics you need first:

  • MRR (monthly recurring revenue): the only revenue number that predicts the future
  • Churn rate: the percentage of paying customers who cancel each month
  • New vs. churned: raw counts that tell you whether growth is real or just noise

Everything else -- LTV, ARPU, net revenue retention -- is a function of these three. Start there and add complexity only when it would change a decision.

Where the data lives

If you set up Stripe subscriptions, your app already handles customer.subscription.created, customer.subscription.updated, and customer.subscription.deleted webhooks. Each event carries the customer's plan, amount, and status.

The missing piece is a table that logs each status change with a timestamp. Without it, you can only see the current state -- not the history you need for trend lines.

A minimal addition to your Drizzle schema:

export const subscriptionEventTable = pgTable('subscription_events', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').references(() => userTable.id),
  event: text('event').notNull(),   // 'created' | 'updated' | 'canceled'
  planId: text('plan_id'),
  amountCents: integer('amount_cents'), // normalized to monthly
  createdAt: timestamp('created_at').defaultNow(),
});

Your Stripe webhook handler inserts a row on each relevant event. That is the entire data collection step -- no cron jobs, no external queue.

Computing MRR from your database

MRR is the sum of amount_cents for all subscriptions where event = 'created' and there is no later event = 'canceled' for the same user. Monthly MRR over time is a GROUP BY month on created_at. That is the series Recharts needs to draw a line chart.

You do not need a separate analytics database. The same Neon DB that stores your users stores your subscription history, and PostgreSQL can aggregate it in milliseconds.

The churn rate calculation

Churn rate for a given month = customers who canceled / customers active at the start of that month.

A 5% monthly churn rate means you lose half your customers in about a year. A 2% rate nearly doubles lifetime value. That single number changes how you think about CAC, marketing spend, and whether it makes sense to run a paid acquisition channel at all.

Building the dashboard page

The dashboard is a Next.js server component. It runs three queries in parallel and renders with data -- no loading spinners, no client-side fetching:

const [mrr, churnRate, newVsCanceled] = await Promise.all([
  subscriptionRepo.getMRRByMonth(),
  subscriptionRepo.getChurnRateByMonth(),
  subscriptionRepo.getNewVsCanceledByMonth(),
]);

Each query returns { month: string; value: number }[]. Pass them directly to Recharts <LineChart> or <BarChart> components.

Protect the route with a role check so only admins see it:

const user = await getUserFromRequest(req);
if (user.role !== 'admin') throw new HttpError(403, 'Forbidden');

When to pay for Baremetrics instead

Baremetrics connects to Stripe and runs the same aggregations you just built. It costs money for the convenience. Here is the honest decision framework:

  • Pre-revenue or early traction: pay for Baremetrics. It is live in 10 minutes and frees you to focus on customers.
  • Consistent MRR, team of 2+: build it yourself. You own the data, you control what you track, and you stop paying a monthly fee for aggregations your own database can run.
  • Need investor-grade metrics reports: use Baremetrics or ChartMogul. Their PDF exports and investor dashboards are genuinely good.

For most indie hackers and early-stage SaaS founders, the internal dashboard wins on cost and flexibility.

The one metric that tells you everything

If you track nothing else, track this: new subscribers minus canceled subscribers, by month.

When that number is positive and growing, you have product-market fit in motion. When it goes flat or negative, you have a problem no dashboard will fix -- but at least you will know before you run out of runway.

The Next.js SaaS boilerplate ships with Stripe webhooks, Drizzle ORM, Recharts, and JWT auth already wired. You are not starting from zero. You are adding one table, three queries, and a protected page.

Get started at boilerplate.iteam-company.com and ship your metrics dashboard this weekend.