Every SaaS that grows without a massive marketing budget has one thing in common: its users talk about it. A referral program turns that word of mouth into something you can measure, reward, and scale.
If you are building a SaaS on Next.js and Drizzle ORM, you already have everything you need. Here is what the system looks like and how to wire it up.
Paid ads are expensive and unpredictable. A referral program is neither. You only pay out when a referred user actually converts -- a paid subscription, a completed onboarding step, or whatever milestone matters to your business. That makes your cost per acquisition predictable and tied directly to revenue.
The other advantage: referred users churn less. They signed up because someone they trust recommended the product, not because they clicked a banner. That trust carries over into how they use it.
You need two additions to your existing Drizzle schema.
First, a referral_code column on the user table -- a short, unique string generated at registration time:
referral_code text not null unique
Second, a referrals table that records who referred who:
referrals
id uuid primary key
referrer_id uuid -- references users.id
referee_id uuid -- references users.id (unique -- one referrer per signup)
converted_at timestamp -- null until the referee converts
rewarded_at timestamp -- null until the referrer is rewarded
created_at timestamp
Two timestamps -- converted_at and rewarded_at -- are enough to drive the entire reward lifecycle without a separate payouts table.
On registration, generate a short code using Node's built-in crypto module:
import { randomBytes } from 'crypto'
function generateReferralCode(): string {
return randomBytes(4).toString('hex') // e.g. "a3f82c10"
}
Eight hex characters give you over 4 billion possible codes. Store it as a text.notNull().unique() column on the user table and add a database index on it so the lookup at signup is fast.
When a visitor arrives at /register?ref=a3f82c10, your registration route reads the ref query param and passes it into the service layer. After the user row is created, the service inserts a row into referrals linking the new user to the referrer who owns that code.
// Inside userService.register()
if (referralCode) {
const referrer = await userRepo.findByReferralCode(referralCode)
if (referrer && referrer.id !== newUser.id) {
await referralRepo.create({
referrerId: referrer.id,
refereeId: newUser.id,
})
}
}
Wrapping this in the same Drizzle transaction as the user insert means you never end up with a signup that lost its referral attribution due to a partial failure. If the referral code is invalid or missing, registration proceeds normally -- no error, just no referral row.
A "conversion" is whatever you define. Paid subscription, verified email, completed onboarding -- the referrals table does not care. In your service, after the conversion event happens, mark the referral row:
await referralRepo.markConverted(referee.id)
// sets converted_at = now() where referee_id = referee.id
When you are ready to pay out -- or unlock a bonus feature for the referrer -- query for referrals where converted_at IS NOT NULL and rewarded_at IS NULL, process the reward, then set rewarded_at. A Vercel cron route running once a day works well for batch processing.
This keeps your payout logic decoupled from your conversion logic. You can change how you reward referrers without touching the attribution or conversion code.
Every user gets a referral link they can copy and share. A server component fetches the data in one query:
const stats = await referralService.getStatsForUser(userId)
// { code: "a3f82c10", total: 12, converted: 5 }
Render a share link -- https://yourapp.com/register?ref={code} -- alongside the stats. No third-party widget needed. Users copy the link and share it wherever they already talk to people: in emails, Slack channels, or social posts.
This entire system runs on your existing stack. No third-party referral service, no JavaScript SDK to maintain, no extra webhook surface to secure. The referrals table is the source of truth. Drizzle ORM queries it. A Vercel cron route processes rewards. Your existing auth layer already has the user context you need.
A referral program is one of the highest-leverage growth tools available to early-stage SaaS founders, and it is cheaper to build than most people expect.
The Next.js SaaS Boilerplate ships with Drizzle ORM, Neon DB, Vercel cron, and JWT auth already wired together. You can add this referral system on top in an afternoon -- no new infrastructure, no new services, no SDK to babysit.
Get the boilerplate and start turning your signups into a growth channel this week.