logowaitly

How to build a viral referral waitlist system in Next.js with Waitly SDK

How to build a viral referral waitlist system in Next.js with Waitly SDK

8/26/2025

Building a successful product launch starts with creating buzz and anticipation. One of the most effective strategies is implementing a referral system in your waitlist that turns your early users into advocates. In this comprehensive guide, we'll show you how to create a powerful referral waitlist system using waitly-sdk in your Next.js application.

Why Referral Waitlists Work

Before diving into the implementation, let's understand why referral systems are so effective:

Companies like Dropbox, Robinhood, and Superhuman have used referral waitlists to build massive user bases before launch.

Setting Up Your Next.js Project with Waitly

Step 1: Install Waitly SDK

First, install the Waitly SDK in your Next.js project:

npm install @go-waitly/waitly-sdk

Step 2: Initialize the Waitly Client

Create a utility file to initialize your Waitly client:

// lib/waitly.js
import { createWaitlyClient } from "@go-waitly/waitly-sdk";

export const waitly = createWaitlyClient({
  apiKey: process.env.WAITLY_API_KEY,
  waitlistId: process.env.WAITLY_WAITLIST_ID,
});

Don't forget to add your environment variables in .env.local:

WAITLY_API_KEY=your_api_key_here
WAITLY_WAITLIST_ID=your_waitlist_id_here

Building the Referral System Components

Step 3: Create the Waitlist Signup Form

Let's build a signup form that handles referral codes automatically:

// components/WaitlistForm.jsx
import { useState } from 'react';
import { useRouter } from 'next/router';
import { waitly } from '../lib/waitly';

export default function WaitlistForm() {
  const [email, setEmail] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [success, setSuccess] = useState(false);
  const router = useRouter();

  // Extract referral code from URL parameters
  const referralCode = router.query.code;

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);

    try {
      const entry = await waitly.createWaitlyEntry({
        email,
        referredByCode: referralCode, // Automatically use referral code from URL
        utm: {
          utm_source: referralCode ? 'referral' : 'organic',
          utm_medium: 'waitlist',
        }
      });

      // Store user data for the success page
      localStorage.setItem('waitlist_entry', JSON.stringify(entry));
      setSuccess(true);
    } catch (error) {
      console.error('Error joining waitlist:', error);
      alert('Something went wrong. Please try again.');
    } finally {
      setIsLoading(false);
    }
  };

  if (success) {
    return <ReferralSuccess />;
  }

  return (
    <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
      <h2 className="text-2xl font-bold mb-4 text-center">
        Join Our Waitlist
      </h2>
      
      {referralCode && (
        <div className="mb-4 p-3 bg-green-50 border border-green-200 rounded">
          <p className="text-green-800 text-sm">
            🎉 You've been invited by a friend! You'll get priority access.
          </p>
        </div>
      )}

      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="Enter your email"
            required
            className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
          />
        </div>
        
        <button
          type="submit"
          disabled={isLoading}
          className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50"
        >
          {isLoading ? 'Joining...' : 'Join Waitlist'}
        </button>
      </form>
    </div>
  );
}

Step 4: Build the Referral Success Page

After users join, show them their referral link and encourage sharing:

// components/ReferralSuccess.jsx
import { useState, useEffect } from 'react';
import { waitly } from '../lib/waitly';

export default function ReferralSuccess() {
  const [entry, setEntry] = useState(null);
  const [copied, setCopied] = useState(false);
  const [stats, setStats] = useState({ position: null, total: 0 });

  useEffect(() => {
    // Get stored entry data
    const storedEntry = localStorage.getItem('waitlist_entry');
    if (storedEntry) {
      const entryData = JSON.parse(storedEntry);
      setEntry(entryData);
      
      // Get position and stats
      fetchStats(entryData.email);
    }
  }, []);

  const fetchStats = async (email) => {
    try {
      const [checkResult, totalCount] = await Promise.all([
        waitly.checkEmail(email),
        waitly.countEntries()
      ]);
      
      setStats({
        position: checkResult.position,
        total: totalCount
      });
    } catch (error) {
      console.error('Error fetching stats:', error);
    }
  };

  const copyReferralLink = () => {
    navigator.clipboard.writeText(entry.referral_link);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  const shareOnTwitter = () => {
    const text = `I just joined the waitlist for this amazing new product! Use my referral link to get priority access: ${entry.referral_link}`;
    window.open(`https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}`, '_blank');
  };

  if (!entry) return <div>Loading...</div>;

  return (
    <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg text-center">
      <div className="mb-6">
        <h2 className="text-2xl font-bold text-green-600 mb-2">
          🎉 You're In!
        </h2>
        <p className="text-gray-600">
          You're #{stats.position} of {stats.total} on the waitlist
        </p>
      </div>

      <div className="mb-6 p-4 bg-blue-50 rounded-lg">
        <h3 className="font-semibold mb-2">Move Up Faster!</h3>
        <p className="text-sm text-gray-600 mb-3">
          Share your referral link and move up 1 position for each friend who joins
        </p>
        
        <div className="flex items-center space-x-2 mb-3">
          <input
            type="text"
            value={entry.referral_link}
            readOnly
            className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded bg-gray-50"
          />
          <button
            onClick={copyReferralLink}
            className="px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700"
          >
            {copied ? 'Copied!' : 'Copy'}
          </button>
        </div>

        <div className="flex space-x-2">
          <button
            onClick={shareOnTwitter}
            className="flex-1 bg-blue-400 text-white py-2 px-4 rounded text-sm hover:bg-blue-500"
          >
            Share on Twitter
          </button>
          <button
            onClick={() => {
              if (navigator.share) {
                navigator.share({
                  title: 'Join this awesome waitlist!',
                  url: entry.referral_link
                });
              }
            }}
            className="flex-1 bg-green-500 text-white py-2 px-4 rounded text-sm hover:bg-green-600"
          >
            Share
          </button>
        </div>
      </div>

      <p className="text-xs text-gray-500">
        Your referral code: <span className="font-mono">{entry.referral_code}</span>
      </p>
    </div>
  );
}
 

Step 5: Create a Referral Status Dashboard

Let users track their referral progress:

// components/ReferralDashboard.jsx
import { useState } from 'react';
import { waitly } from '../lib/waitly';

export default function ReferralDashboard() {
  const [email, setEmail] = useState('');
  const [userData, setUserData] = useState(null);
  const [loading, setLoading] = useState(false);

  const checkStatus = async (e) => {
    e.preventDefault();
    setLoading(true);

    try {
      const [checkResult, totalCount] = await Promise.all([
        waitly.checkEmail(email),
        waitly.countEntries()
      ]);

      setUserData({
        ...checkResult,
        totalEntries: totalCount
      });
    } catch (error) {
      console.error('Error checking status:', error);
      alert('Email not found in waitlist');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-lg">
      <h2 className="text-2xl font-bold mb-4 text-center">
        Check Your Status
      </h2>

      <form onSubmit={checkStatus} className="space-y-4 mb-6">
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Enter your email"
          required
          className="w-full px-4 py-2 border border-gray-300 rounded-md"
        />
        <button
          type="submit"
          disabled={loading}
          className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50"
        >
          {loading ? 'Checking...' : 'Check Status'}
        </button>
      </form>

      {userData && (
        <div className="space-y-4">
          <div className="text-center p-4 bg-gray-50 rounded">
            <p className="text-2xl font-bold">#{userData.position}</p>
            <p className="text-gray-600">of {userData.totalEntries}</p>
          </div>

          <div className="p-4 bg-blue-50 rounded">
            <h3 className="font-semibold mb-2">Your Referral Link:</h3>
            <div className="flex items-center space-x-2">
              <input
                type="text"
                value={userData.referral_link || `https://yoursite.com/?code=${userData.referral_code}`}
                readOnly
                className="flex-1 px-3 py-2 text-sm border border-gray-300 rounded bg-white"
              />
              <button
                onClick={() => navigator.clipboard.writeText(userData.referral_link)}
                className="px-4 py-2 bg-blue-600 text-white text-sm rounded hover:bg-blue-700"
              >
                Copy
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

Best Practices for Waitlist Referrals

1. Clear Value Proposition

2. Friction-Free Sharing

3. Progress Visualization

4. Mobile Optimization

Common Pitfalls to Avoid

  1. Complicated Referral Process: Keep it simple - one click to get a link

  2. Unclear Benefits: Users should immediately understand what they gain

  3. Poor Mobile Experience: Most sharing happens on mobile devices

  4. No Progress Feedback: Users need to see their referrals working

  5. Generic Share Messages: Personalize the sharing experience

Conclusion

Implementing a referral system in your Next.js waitlist using Waitly SDK is straightforward and powerful. The headless approach gives you complete control over the user experience while handling the complex backend logic.

Ready to implement your own referral waitlist? Get started with Waitly and turn your users into your best growth channel.