Add a Waitlist and Launch Page to Your Boilerplate 2026
·StarterPick Team
waitlistlaunchmarketingnextjsguide2026
TL;DR
A waitlist page buys you time to build while growing demand. The minimum viable waitlist is: email form → confirmation email → simple admin view. Add referral mechanics later if you want viral growth. Total setup: 1 day. This guide uses Resend for emails and your existing database — no external tools required.
Option 1: DIY Waitlist (Recommended)
Database Schema
// prisma/schema.prisma
model WaitlistEntry {
id String @id @default(cuid())
email String @unique
name String?
referredBy String? // WaitlistEntry.id of referrer
referralCode String @unique @default(cuid())
referralCount Int @default(0)
position Int // Calculated based on referrals
source String? // 'twitter', 'ph', 'direct'
confirmed Boolean @default(false)
invitedAt DateTime?
createdAt DateTime @default(now())
}
Waitlist Signup API
// app/api/waitlist/route.ts
import { prisma } from '@/lib/prisma';
import { sendWaitlistConfirmation } from '@/lib/email';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
name: z.string().optional(),
referralCode: z.string().optional(),
source: z.string().optional(),
});
export async function POST(req: Request) {
const body = await req.json();
const { email, name, referralCode, source } = schema.parse(body);
// Check existing
const existing = await prisma.waitlistEntry.findUnique({ where: { email } });
if (existing) {
return Response.json({
success: true,
position: existing.position,
referralCode: existing.referralCode,
message: "You're already on the waitlist!",
});
}
// Find referrer
let referrer = null;
if (referralCode) {
referrer = await prisma.waitlistEntry.findUnique({
where: { referralCode },
});
}
// Get current count for position
const count = await prisma.waitlistEntry.count();
// Create entry
const entry = await prisma.waitlistEntry.create({
data: {
email,
name,
referredBy: referrer?.id,
position: count + 1,
source,
},
});
// Bump referrer's count and position
if (referrer) {
await prisma.waitlistEntry.update({
where: { id: referrer.id },
data: {
referralCount: { increment: 1 },
position: { decrement: 5 }, // Move up 5 spots per referral
},
});
}
// Send confirmation email
await sendWaitlistConfirmation(entry);
return Response.json({
success: true,
position: entry.position,
referralCode: entry.referralCode,
});
}
Waitlist Landing Page
// app/page.tsx — pre-launch landing page
'use client';
import { useState } from 'react';
export default function WaitlistPage() {
const [email, setEmail] = useState('');
const [submitted, setSubmitted] = useState(false);
const [position, setPosition] = useState<number | null>(null);
const [referralCode, setReferralCode] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
// Get referral code from URL if present
const urlCode = new URLSearchParams(window.location.search).get('ref');
const res = await fetch('/api/waitlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, referralCode: urlCode }),
});
const data = await res.json();
setPosition(data.position);
setReferralCode(data.referralCode);
setSubmitted(true);
setLoading(false);
}
if (submitted && position && referralCode) {
const referralUrl = `${window.location.origin}?ref=${referralCode}`;
return (
<div className="max-w-md mx-auto text-center py-20">
<h2 className="text-2xl font-bold mb-2">You're #Waitlist{position}! 🎉</h2>
<p className="text-gray-600 mb-6">
Share your link to move up the waitlist — each referral bumps you 5 spots.
</p>
<div className="bg-gray-50 rounded-lg p-4 mb-4">
<p className="text-sm text-gray-500 mb-2">Your referral link</p>
<p className="font-mono text-sm break-all">{referralUrl}</p>
</div>
<button
onClick={() => navigator.clipboard.writeText(referralUrl)}
className="bg-indigo-600 text-white px-6 py-2 rounded-lg"
>
Copy Link
</button>
</div>
);
}
return (
<div className="max-w-2xl mx-auto text-center py-20 px-4">
<div className="inline-block bg-indigo-100 text-indigo-700 text-sm px-3 py-1 rounded-full mb-4">
Coming soon
</div>
<h1 className="text-5xl font-bold tracking-tight mb-4">
[Your SaaS Tagline Here]
</h1>
<p className="text-xl text-gray-500 mb-8">
[One sentence describing your product's core value.]
</p>
<form onSubmit={handleSubmit} className="flex gap-2 max-w-sm mx-auto">
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="you@example.com"
required
className="flex-1 border border-gray-300 rounded-lg px-4 py-2"
/>
<button
type="submit"
disabled={loading}
className="bg-indigo-600 text-white px-6 py-2 rounded-lg"
>
{loading ? '...' : 'Join Waitlist'}
</button>
</form>
<p className="text-gray-400 text-sm mt-3">
Join 2,400+ people waiting for early access.
</p>
</div>
);
}
Confirmation Email
// emails/WaitlistConfirmationEmail.tsx
export function WaitlistConfirmationEmail({
name,
position,
referralUrl,
}: {
name?: string;
position: number;
referralUrl: string;
}) {
return (
<EmailLayout preview={`You're #${position} on the waitlist!`}>
<Heading>You're on the list! 🎉</Heading>
<Text>Hi {name ?? 'there'},</Text>
<Text>
You're <strong>#{position}</strong> on the waitlist. We'll send you early access
when we're ready to launch.
</Text>
<Text>
Want to move up? Share your referral link — each signup bumps you 5 spots:
</Text>
<Button href={referralUrl}>Share Your Link →</Button>
</EmailLayout>
);
}
Admin: Invite Users from Waitlist
// app/admin/waitlist/actions.ts
export async function inviteFromWaitlist(entryId: string) {
const entry = await prisma.waitlistEntry.findUnique({
where: { id: entryId },
});
if (!entry) throw new Error('Entry not found');
// Create user account
const user = await prisma.user.create({
data: {
email: entry.email,
name: entry.name,
},
});
// Send magic link or temporary password
await sendInvitationEmail(user.email, entry.name);
// Mark as invited
await prisma.waitlistEntry.update({
where: { id: entryId },
data: { invitedAt: new Date() },
});
}
Option 2: Managed Waitlist Tools
If you don't want to build it:
| Tool | Cost | Features |
|---|---|---|
| Waitlist.email | Free → $29/mo | Referral system, embeddable |
| Tally.so | Free → $29/mo | Form builder with waitlist mode |
| Mailchimp | Free → $13/mo | Basic email collection |
| Loops.so | Free → $49/mo | Email + waitlist + sequences |
For most solo founders, Tally + Loops is fastest and free to start.
Converting Waitlist → Paid
When you're ready to launch:
- Segment by referrals — top referrers become your initial power users and advocates
- Create urgency — "First 100 users get 50% off forever"
- Announce with specifics — exact launch date, not "soon"
- Give early access in batches — 50 users per wave prevents support overload
- Offer Lifetime Deal — waitlist-only pricing builds goodwill
Find boilerplates with waitlist features built-in on StarterPick.
Check out this boilerplate
View ShipFast on StarterPick →