Launch

Building a waitlist with referrals in Next.js

We earn commissions when you shop through the links below.

Collect emails, issue a referral code, and compute a queue position with Prisma in a Next.js server action.

3 min read

This article contains affiliate links. If you buy through them, Designyff may earn a commission at no extra cost to you.

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.

Flipping launched does not send anything. To announce the launch, import that CSV into an email tool. Kit is one option: its CSV import needs only an email column and can map refCode and referredBy to custom fields, so the launch email can thank the people who brought others in. Its free plan covers up to 10,000 subscribers at the time of writing. To send each signup to Kit as it happens instead of exporting a CSV at launch, see adding a Kit newsletter signup to Next.js.

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.

Product updates for developers shipping with Designyff kits.

Guides: Next.js, Stripe, and SaaS starter kits Professional website design for small businesses

Purchased source may be used in personal, commercial, and client projects. Do not redistribute or resell the original source. Terms of Service Privacy Policy

Designyff © 2026 | Starter kits for developers