Launch
Building a waitlist with referrals in Next.js
Collect emails, issue a referral code, and compute a queue position with Prisma in a Next.js server action.
A referral waitlist needs three facts per signup: the email, a code they can share, and the code that brought them in. The Waitlist / Launch Kit stores those on one Prisma model and uses SQLite.
Schema
model Signup {
id String @id @default(cuid())
email String @unique
refCode String @unique
referredBy String?
createdAt DateTime @default(now())
}
model Settings {
id Int @id @default(1)
launched Boolean @default(false)
}
referredBy is the referrer’s refCode, not a foreign key. A bad or missing ref is stored as null. The kit does not walk a referral tree or pay anyone.
Join action
const referredBy = String(formData.get("ref") || "") || null;
const refCode = crypto.randomBytes(4).toString("hex");
randomBytes(4) is 8 hex characters. The page passes the current ?ref= query into a hidden input, so the server action sees it without reading the URL again. The email is unique. Signing up twice with the same address should hit that constraint; handle the Prisma error and send the person back to their existing code instead of showing a 500.
After insert, the action redirects to /?code= plus the new refCode.
Position
Position is not a stored rank. The page loads the signup by code and counts rows created earlier:
const ahead = await prisma.signup.count({
where: { createdAt: { lt: mine.createdAt } }
});
const position = (ahead || 0) + 1;
The share link is https://{host}/?ref={refCode}, where host comes from the request headers. Two signups in the same millisecond can share a timestamp; the count is then a tie, not a stable order. That is acceptable for a launch page. Do not promise a locked rank unless you store an integer sequence.
Launch flag and export
Settings.launched flips the heading between “Get in early.” and “We are live.” The admin page lists signups and links to /api/export, which returns CSV columns email,refCode,referredBy. Protect that route with the admin token the kit already checks. A public export is a mailing list.
The public page also shows signup.count() as “N people on the list.” That count includes everyone, not only people referred by the current visitor.
Want to skip the setup? The Waitlist / Launch Kit already includes the form, referral codes, position, CSV export, and the launch toggle.