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.
Trusted by 2,000+
businesses & entrepreneurs
.png?alt=media&token=939637fa-d391-4d15-85ea-7005e07d08eb)







.png?alt=media&token=264537c9-b2e0-44e7-9d78-3b558d4e10c2)






.png?alt=media&token=939637fa-d391-4d15-85ea-7005e07d08eb)







.png?alt=media&token=264537c9-b2e0-44e7-9d78-3b558d4e10c2)






“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.”

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.
Multi-Tenant SaaS Waitlists
Dynamic waitlist pages with SSG for each subdomain or custom domain. Perfect for white-label products.
High-Performance Marketing Sites
Marketing pages with 100/100 Lighthouse scores. Edge-deployed for global performance.
API-First Integrations
Backend API routes for headless CMS, mobile apps, or custom integrations. Serverless functions for complex logic.
Progressive Web Apps
Build installable PWAs with offline-capable waitlist forms and background sync.
Analytics-Driven Launches
Track everything with Vercel Analytics, Google Analytics, or custom tracking. A/B test landing pages.
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.
Which integration is
right for you?
Compare both methods to find the best fit for your Next.js project
Feature | Form Action | Embeddable Widget |
---|---|---|
Setup Complexity | Moderate (API route) | Simple (one component) |
SSR/SSG Support | Full control | Works with both |
Server Actions | Supported (App Router) | N/A |
SEO Optimization | Excellent | Good |
Backend Integration | Full (API routes) | Limited |
Edge Compatible | Yes | Yes |
Best For | Production apps | Quick 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
How to integrate
Follow these Next.js-specific instructions
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
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' });
}
}
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';
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>
);
}
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>
);
}
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*',
};
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
Need more details?
Check out our complete form action endpoint documentation.
View full documentationCommon 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.
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.
Get started for free
Start collecting sign ups for your
product launch in minutes — no coding required.