Next.js

Build SEO-optimized waitlist pages that rank and convert

Production-grade Next.js integration with SSR, SSG, and App Router support. Deploy blazing-fast waitlist pages on Vercel with perfect Lighthouse scores. Server Components and Server Actions ready.

Next.js + Waitlister

Trusted by 2,000+
businesses & entrepreneurs

Data Hokage logo
Data Hokage
Fink Academy logo
Fink Academy
stagewise logo
stagewise
Sirius AI logo
Sirius AI
BLADNA logo
BLADNA
PagePal logo
PagePal
ChatAce.io logo
ChatAce.io
Instanote logo
Instanote
DirectoryDeck logo
DirectoryDeck
landman® logo
landman®
datapro logo
datapro
NATRU logo
NATRU
Pop Date logo
Pop Date
Aspire logo
Aspire
WalletX logo
WalletX
quickblogs logo
quickblogs
Data Hokage logo
Data Hokage
Fink Academy logo
Fink Academy
stagewise logo
stagewise
Sirius AI logo
Sirius AI
BLADNA logo
BLADNA
PagePal logo
PagePal
ChatAce.io logo
ChatAce.io
Instanote logo
Instanote
DirectoryDeck logo
DirectoryDeck
landman® logo
landman®
datapro logo
datapro
NATRU logo
NATRU
Pop Date logo
Pop Date
Aspire logo
Aspire
WalletX logo
WalletX
quickblogs logo
quickblogs
“I can only say good things about Waitlister. Their landing page is very user friendly, and Devin (the owner) directly answers your emails very rapidly. Waitlister's pricing is more than reasonable.”
Trading Revolution logo
Pierre Rabinowitz
Founder, Trading Revolution
Use Cases

What you can build

Popular ways Next.js users implement waitlists

SEO-First Product Launches

Build pre-launch pages that rank in Google before your product even launches. SSR and metadata for perfect SEO.

Example: Static landing page with ISR for real-time signup counts

Multi-Tenant SaaS Waitlists

Dynamic waitlist pages with SSG for each subdomain or custom domain. Perfect for white-label products.

Example: [tenant].yoursaas.com with unique waitlists per customer

High-Performance Marketing Sites

Marketing pages with 100/100 Lighthouse scores. Edge-deployed for global performance.

Example: Marketing site with waitlist served from Edge Network worldwide

API-First Integrations

Backend API routes for headless CMS, mobile apps, or custom integrations. Serverless functions for complex logic.

Example: API endpoint that validates, processes, then submits to Waitlister

Progressive Web Apps

Build installable PWAs with offline-capable waitlist forms and background sync.

Example: PWA with Service Worker caching waitlist submissions for offline

Analytics-Driven Launches

Track everything with Vercel Analytics, Google Analytics, or custom tracking. A/B test landing pages.

Example: Multiple landing page variants with conversion tracking and analytics
Benefits

Why Waitlister for Next.js?

Built to work seamlessly with Next.js's capabilities

SSR & SSG Native

Full support for Server-Side Rendering, Static Site Generation, and Incremental Static Regeneration. Build SEO-perfect pages that load instantly.

App Router & Server Components

Next.js 13+ App Router compatible with Server Components, Server Actions, and streaming. Pages Router also fully supported for existing projects.

Edge-Ready Architecture

Deploy to Vercel Edge, Cloudflare Workers, or any edge platform. Serve waitlist pages from 200+ global locations with sub-50ms response times.

Built-in SEO Optimization

Perfect metadata API support. Structured data, Open Graph, Twitter Cards all automatically handled. Rank higher in search results before launch.

API Routes for Backend Logic

Use Next.js API routes or Route Handlers for custom validation, rate limiting, database logging, or webhook integrations. Full backend control.

Production-Grade DX

TypeScript-first, hot module replacement, Fast Refresh. Deploy in seconds to Vercel with zero config. Automatic HTTPS and global CDN.

Choose Your Method

Which integration is
right for you?

Compare both methods to find the best fit for your Next.js project

FeatureForm ActionEmbeddable Widget
Setup ComplexityModerate (API route)Simple (one component)
SSR/SSG SupportFull controlWorks with both
Server ActionsSupported (App Router)N/A
SEO OptimizationExcellentGood
Backend IntegrationFull (API routes)Limited
Edge CompatibleYesYes
Best ForProduction appsQuick MVPs

Choose Form Action if...

  • You need complete control over the submission flow
  • You want to add custom validation or rate limiting
  • You need to log submissions to your own database
  • You're building a production SaaS application
  • You want to use Server Actions in App Router
  • You need to integrate with webhooks or third-party APIs

Choose Embeddable Widget if...

  • You need the fastest setup possible
  • You're building a simple landing page
  • You don't need custom backend logic
  • You want a pre-styled form component
  • You're prototyping or validating an idea
Step-by-Step Guide

How to integrate

Follow these Next.js-specific instructions

1

Set up environment variables

Add your Waitlister key to .env.local:

# .env.local
NEXT_PUBLIC_WAITLIST_KEY=your_key_here

# For API routes (server-only)
WAITLIST_KEY=your_key_here
Pro tip
NEXT_PUBLIC_ prefix makes variables available in browser. Without it, they're server-only.
2

Create API route (Pages Router)

Build an API endpoint to handle submissions with custom logic:

// pages/api/waitlist.ts
import type { NextApiRequest, NextApiResponse } from 'next';

type ResponseData = {
  success: boolean;
  message: string;
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ success: false, message: 'Method not allowed' });
  }

  const { email, name } = req.body;

  // Custom validation
  if (!email || !email.includes('@')) {
    return res.status(400).json({ success: false, message: 'Invalid email' });
  }

  // Rate limiting (simple example)
  // In production, use upstash/redis or similar
  
  try {
    // Forward to Waitlister
    const response = await fetch(
      `https://waitlister.me/s/${process.env.WAITLIST_KEY}`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({ email, name })
      }
    );

    if (!response.ok) {
      throw new Error('Submission failed');
    }

    // Optional: Log to your database, send to analytics, etc.
    
    return res.status(200).json({ success: true, message: 'Successfully joined waitlist' });
  } catch (error) {
    console.error('Waitlist error:', error);
    return res.status(500).json({ success: false, message: 'Failed to join waitlist' });
  }
}
Pro tip
API routes run on the server, so you can add any backend logic: database logging, validation, rate limiting, webhooks, etc.
3

Create Route Handler (App Router)

For App Router, use Route Handlers instead of API routes:

// app/api/waitlist/route.ts
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const { email, name } = body;

    // Validation
    if (!email || !email.includes('@')) {
      return NextResponse.json(
        { success: false, message: 'Invalid email' },
        { status: 400 }
      );
    }

    // Forward to Waitlister
    const response = await fetch(
      `https://waitlister.me/s/${process.env.WAITLIST_KEY}`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({ email, name })
      }
    );

    if (!response.ok) {
      throw new Error('Submission failed');
    }

    return NextResponse.json({ 
      success: true, 
      message: 'Successfully joined waitlist' 
    });
  } catch (error) {
    console.error('Waitlist error:', error);
    return NextResponse.json(
      { success: false, message: 'Failed to join waitlist' },
      { status: 500 }
    );
  }
}

// Enable Edge Runtime for global performance
export const runtime = 'edge';
Pro tip
Route Handlers can run on Edge Runtime for sub-50ms global response times.
4

Build client form component

Create a client component that submits to your API:

// components/WaitlistForm.tsx
'use client';

import { useState, FormEvent } from 'react';

export default function WaitlistForm() {
  const [email, setEmail] = useState('');
  const [name, setName] = useState('');
  const [loading, setLoading] = useState(false);
  const [success, setSuccess] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/waitlist', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, name })
      });

      const data = await response.json();

      if (data.success) {
        setSuccess(true);
        setEmail('');
        setName('');
      } else {
        setError(data.message || 'Failed to join waitlist');
      }
    } catch (err) {
      setError('An error occurred. Please try again.');
    } finally {
      setLoading(false);
    }
  };

  if (success) {
    return (
      <div className="success-message">
        <h3>You're on the list! 🎉</h3>
        <p>We'll notify you when we launch.</p>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit} className="waitlist-form">
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Your email"
        required
      />
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Your name (optional)"
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Joining...' : 'Join Waitlist'}
      </button>
      {error && <div className="error-message">{error}</div>}
    </form>
  );
}
Pro tip
The 'use client' directive is required for interactive components in App Router.
5

Server Actions (App Router only)

For the most modern approach, use Server Actions:

// app/actions/waitlist.ts
'use server';

export async function submitToWaitlist(formData: FormData) {
  const email = formData.get('email') as string;
  const name = formData.get('name') as string;

  // Validation
  if (!email || !email.includes('@')) {
    return { success: false, message: 'Invalid email' };
  }

  try {
    const response = await fetch(
      `https://waitlister.me/s/${process.env.WAITLIST_KEY}`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({ email, name })
      }
    );

    if (!response.ok) throw new Error('Submission failed');

    return { success: true, message: 'Successfully joined!' };
  } catch (error) {
    return { success: false, message: 'Failed to join waitlist' };
  }
}

// Use in component:
// 'use client';
import { submitToWaitlist } from '@/app/actions/waitlist';
import { useFormStatus } from 'react-dom';

export default function WaitlistForm() {
  return (
    <form action={submitToWaitlist}>
      <input type="email" name="email" required />
      <input type="text" name="name" />
      <SubmitButton />
    </form>
  );
}

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Joining...' : 'Join Waitlist'}
    </button>
  );
}
Pro tip
Server Actions are the future of Next.js forms - no API routes needed!
6

Configure CORS for API routes

If calling your API from external sources, add CORS headers:

// middleware.ts (App Router)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const response = NextResponse.next();

  // Add CORS headers if needed
  response.headers.set('Access-Control-Allow-Origin', '*');
  response.headers.set('Access-Control-Allow-Methods', 'POST');
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type');

  return response;
}

export const config = {
  matcher: '/api/:path*',
};
Pro tip
Only add CORS if you need cross-origin requests. Internal calls don't need it.
7

Deploy to Vercel with environment variables

Add your environment variables in Vercel dashboard or via CLI:

# Using Vercel CLI
vercel env add WAITLIST_KEY
vercel env add NEXT_PUBLIC_WAITLIST_KEY

# Then deploy
vercel --prod
Pro tip
Vercel automatically loads .env.local in development. For production, add variables in project settings.

Need more details?

Check out our complete form action endpoint documentation.

View full documentation
Troubleshooting

Common issues & solutions

Quick fixes for Next.js-specific problems

For Pages Router, ensure script is in _document.tsx. For App Router, add to layout.tsx. Check browser Network tab to verify script loads. If using next/script, make sure strategy="lazyOnload" or "afterInteractive" is set.

Client-side variables must have NEXT_PUBLIC_ prefix. Server-only variables (API routes) don't need the prefix. Restart dev server after adding new variables. Check .env.local is in .gitignore.

Pages Router: API routes must be in pages/api/ directory. App Router: Route handlers must be in app/api/ with route.ts filename. Check file extension (.ts not .tsx for handlers).

Next.js dev server runs on localhost, production on your domain. Make sure to whitelist both in Waitlister settings. For API routes called from external sources, add CORS headers in middleware.

Check environment variables are set in Vercel/deployment platform. Verify API route or Server Action is deployed (check deployment logs). Test API endpoint directly with curl or Postman.

Server Actions require Next.js 13.4+ and React 18+. Ensure 'use server' directive is at top of file or function. Check experimental.serverActions is enabled in next.config.js (enabled by default in 13.4+).

API routes and Server Actions don't work with static export (output: 'export'). Use SSR or SSG deployment instead. For static sites, use client-side submission directly to Waitlister.

Ensure you have proper type definitions. For API routes, import NextApiRequest/NextApiResponse. For Route Handlers, use Request/NextResponse types from next/server.

If getting hydration errors, the embed widget might render differently on server vs client. Use dynamic import with ssr: false for the component containing the embed.

FAQ

Common questions

About Next.js integration

Yes! Both integration methods work perfectly with Next.js 13+ App Router and the traditional Pages Router. We provide specific examples for each routing system.

Absolutely! Server Actions are the most modern approach in Next.js 14+. We provide complete examples using the 'use server' directive with full TypeScript support.

Yes! The embed method works great with SSG. For custom forms with API routes, you'll need SSR or ISR. With App Router, pages are static by default and forms work perfectly.

Use the Metadata API in App Router for perfect SEO. Include structured data, Open Graph tags, and Twitter Cards. Static generation ensures instant loads and perfect Lighthouse scores for maximum ranking.

Yes! Both methods work on Edge Runtime. Add export const runtime = 'edge' to your Route Handlers for sub-50ms global response times. Perfect for international audiences.

Yes! Use ISR to rebuild your waitlist page periodically. Perfect for showing live signup counts or dynamic content while maintaining static performance. Add revalidate: 3600 to regenerate hourly.

Yes! Add custom logic in middleware before form submission - authentication, rate limiting, geolocation, A/B testing, or custom redirects. Full middleware examples provided.

Implement rate limiting in API routes or Server Actions using libraries like @upstash/ratelimit with Vercel KV, or roll your own with Redis. Check IP addresses and limit submissions per time window.

Absolutely! In your API route or Server Action, log submissions to your database (Postgres, MongoDB, etc.) before or after sending to Waitlister. Perfect for analytics or custom dashboards.

Yes! Perfect for product launches in Next.js Commerce stores. Add waitlists to product pages, collection pages, or create dedicated launch pages. Works with all commerce templates.

Use tools like Postman, curl, or even fetch in browser console to test API routes. Or use the form component itself. Next.js dev server runs API routes on http://localhost:3000/api/*

Yes! Each zone can have its own waitlist implementation. Use separate waitlist keys per zone or share one. Perfect for micro-frontend architectures or multi-tenant setups.

Explore

More integrations

Explore other platforms

Get started for free

Start collecting sign ups for your product launch in minutes — no coding required.