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

Product Analytics Without Amplitude -- Track Feature Usage and Funnels in Your Next.js SaaS

August 2, 2026
nextjsdrizzle-ormsaasanalyticsstartup

Most founders find out which features matter by asking users directly. That works until you have 50 accounts and genuinely do not know if anyone has clicked the export button you spent three days building. You could run a survey, or you could just know.

Amplitude solves this, but it starts at $0 and quickly climbs past $200 a month once you have real traffic and want real answers. Mixpanel is similar. These tools are worth the cost at scale. Before you reach scale, you are paying for enterprise reporting when what you actually need is "did users reach step 3 of onboarding?"

Here is how to build that without a third-party tool.

What "product analytics" actually means for an early-stage SaaS

A metrics dashboard (MRR, churn, signups) tells you the health of your business. Product analytics tells you what users do inside your product -- which features they use, where they get stuck, and whether they complete key actions like inviting a teammate or upgrading to a paid plan.

The output is not a single number. It is answers to questions like:

  • 60% of users sign up and never come back. What did the other 40% do differently?
  • The export feature was supposed to be popular. Has anyone used it more than once?
  • Users who upgrade to paid -- what did they do in their first week that churned users did not?

You do not need Amplitude for this. You need one table and one function.

Add an events table to Drizzle ORM

In your schema file, add:

export const eventTable = pgTable('events', {
  id: uuid('id').defaultRandom().primaryKey(),
  userId: uuid('user_id').references(() => userTable.id, { onDelete: 'set null' }),
  name: varchar('name', { length: 100 }).notNull(),
  properties: jsonb('properties').default({}),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

name is the event name -- feature_used, onboarding_completed, export_clicked. properties holds any extra context as JSON: plan name, resource type, count, whatever helps you answer the question later.

Run npm run db:migrate to apply it.

The tracking call

Add a trackEvent function in your service layer:

export async function trackEvent(
  userId: string | null,
  name: string,
  properties?: Record<string, unknown>
) {
  await db.insert(eventTable).values({ userId, name, properties });
}

Call it from your services, never from API routes. If a user exports a CSV, the export service calls trackEvent(userId, 'csv_exported', { recordCount: rows.length }). That is the whole integration.

Getting useful answers

You do not need a dashboard to get value from this. A few queries cover most early questions.

Feature adoption -- how many unique users have ever triggered a specific action:

SELECT COUNT(DISTINCT user_id) FROM events WHERE name = 'csv_exported';

Onboarding funnel -- what percentage of users who completed onboarding also invited a teammate:

SELECT
  COUNT(DISTINCT e1.user_id) AS completed_onboarding,
  COUNT(DISTINCT e2.user_id) AS also_invited
FROM events e1
LEFT JOIN events e2
  ON e1.user_id = e2.user_id AND e2.name = 'teammate_invited'
WHERE e1.name = 'onboarding_completed';

Run these in Drizzle Studio (npm run db:studio) or surface them in your admin panel as saved queries. No charting library required to find the answers -- the numbers tell you what to do.

When this approach breaks down

This setup has limits. Once you track hundreds of event types across tens of thousands of users, querying raw events slows down and you need indexes. Complex cohort analysis -- users who did X then Y within 7 days -- is possible but verbose to write.

Those are good problems to have. They mean you have enough users that a real analytics tool is worth the cost. For now, the cost of this approach is one migration, one function, and the habit of calling it from your services.

Pair it with your existing metrics

Event data becomes more useful alongside the business numbers you already track. The SaaS metrics dashboard shows MRR, churn, and LTV -- add event queries to it and you can see whether churned users ever used your core feature at all. If they did not, that is a retention problem. If they did, that is a pricing problem. That distinction shapes everything you build next.

You can also layer this on top of A/B testing: run a test on your onboarding flow, then use event tracking to measure not just conversion but what converted users actually do in week two. That combination -- experiment then observe -- tells you whether a change is genuinely better or just produces a different kind of disengaged user.


Next.js SaaS Boilerplate includes Drizzle ORM with Neon DB wired up and ready to extend. Add the events table, drop in the tracking call, and you go from "I think users like this" to "I know users use this" -- before you spend another week building a feature no one will click. Get the boilerplate and start shipping with data this weekend.