Next.js Integration
Add a MakeEmWait waitlist to your Next.js application with App Router.
This guide covers four patterns for integrating MakeEmWait with Next.js App Router: the embed widget, server-side API calls, API route proxies, and client-side signup forms.
Embed Widget (Client Component)
The fastest way — drop in the pre-built widget with one component.
// components/WaitlistWidget.tsx
'use client';
import { useEffect, useRef } from 'react';
interface WaitlistWidgetProps {
waitlistId: string;
theme?: 'dark' | 'light' | 'glass' | 'auto';
compact?: boolean;
}
export default function WaitlistWidget({
waitlistId,
theme = 'dark',
compact = false
}: WaitlistWidgetProps) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://makeemwait.com/assets/js/embed.js';
script.setAttribute('data-waitlist-id', waitlistId);
script.setAttribute('data-theme', theme);
if (compact) script.setAttribute('data-compact', 'true');
containerRef.current?.appendChild(script);
return () => {
if (containerRef.current) {
containerRef.current.innerHTML = '';
}
};
}, [waitlistId, theme, compact]);
return <div ref={containerRef} />;
}
The component dynamically injects the embed script into the DOM when it mounts, and cleans up on unmount. Because the script manipulates the DOM directly, the 'use client' directive is required.
Use it in any page:
// app/waitlist/page.tsx
import WaitlistWidget from '@/components/WaitlistWidget';
export default function WaitlistPage() {
return (
<main>
<h1>Join Our Waitlist</h1>
<WaitlistWidget waitlistId="abc123" theme="auto" />
</main>
);
}
The theme="auto" option follows the user’s system light/dark preference. See the Embed Widget docs for all available data- attributes.
NEXT_PUBLIC_ prefix so they're only accessible server-side.
Server-Side API Calls (API Key Auth)
For admin dashboards and server-rendered pages that need authenticated data.
// lib/makeemwait.ts
const API_URL = 'https://makeemwait.com/api/v1';
const API_KEY = process.env.MAKEEMWAIT_API_KEY!;
export async function getWaitlistStats(waitlistId: string) {
const response = await fetch(
`${API_URL}/waitlists/${waitlistId}/stats`,
{
headers: { 'x-api-key': API_KEY },
next: { revalidate: 60 }, // Cache for 60 seconds
}
);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
export async function listSignups(waitlistId: string, limit = 50) {
const response = await fetch(
`${API_URL}/waitlists/${waitlistId}/signups?limit=${limit}`,
{
headers: { 'x-api-key': API_KEY },
cache: 'no-store', // Always fresh
}
);
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
The next: { revalidate: 60 } option tells Next.js to cache the response and revalidate it after 60 seconds, which works well for stats that don’t need to be real-time. Use cache: 'no-store' when you need fresh data on every request.
Use these functions in a Server Component:
// app/admin/page.tsx
import { getWaitlistStats, listSignups } from '@/lib/makeemwait';
export default async function AdminPage() {
const [stats, signups] = await Promise.all([
getWaitlistStats('abc123'),
listSignups('abc123', 10),
]);
return (
<main>
<h1>Waitlist Dashboard</h1>
<p>Total signups: {stats.total_signups}</p>
<ul>
{signups.signups.map((s: any) => (
<li key={s.email}>{s.email} — #{s.position}</li>
))}
</ul>
</main>
);
}
Because this is a Server Component (the default in App Router), the API key stays on the server and is never sent to the browser.
API Route Proxy
When client components need authenticated data without exposing your API key.
// app/api/waitlist/signups/route.ts
import { NextRequest, NextResponse } from 'next/server';
const API_URL = 'https://makeemwait.com/api/v1';
const API_KEY = process.env.MAKEEMWAIT_API_KEY!;
export async function GET(request: NextRequest) {
const waitlistId = request.nextUrl.searchParams.get('waitlist_id');
if (!waitlistId) {
return NextResponse.json({ error: 'Missing waitlist_id' }, { status: 400 });
}
const response = await fetch(
`${API_URL}/waitlists/${waitlistId}/signups?limit=50`,
{ headers: { 'x-api-key': API_KEY } }
);
const data = await response.json();
return NextResponse.json(data, { status: response.status });
}
Client components can then call /api/waitlist/signups?waitlist_id=abc123 without ever seeing the API key. Consider adding your own authentication to this route so it isn’t publicly accessible.
Client-Side Signup Form
For custom-designed signup forms that call the public API directly.
// components/SignupForm.tsx
'use client';
import { useState } from 'react';
import { useSearchParams } from 'next/navigation';
export default function SignupForm({ waitlistId }: { waitlistId: string }) {
const searchParams = useSearchParams();
const [email, setEmail] = useState('');
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [position, setPosition] = useState<number | null>(null);
const [error, setError] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setStatus('loading');
try {
const response = await fetch(
`https://makeemwait.com/api/v1/public/waitlists/${waitlistId}/signups`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
...(searchParams.get('ref') && {
referred_by_token: searchParams.get('ref')
}),
}),
}
);
const data = await response.json();
if (!response.ok) throw new Error(data.error);
setPosition(data.position);
setStatus('success');
} catch (err) {
setError(err instanceof Error ? err.message : 'Something went wrong');
setStatus('error');
}
};
if (status === 'success') {
return <p>You're #{position} on the waitlist!</p>;
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
/>
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Joining...' : 'Join Waitlist'}
</button>
{error && <p>{error}</p>}
</form>
);
}
The form automatically picks up the ref query parameter from the URL for referral tracking. When someone visits yoursite.com/waitlist?ref=TOKEN, the referral token is included in the signup request so the referrer gets credit.
Environment Variables
Add your API key to .env.local in the root of your Next.js project:
# .env.local
MAKEEMWAIT_API_KEY=mew_live_your_api_key_here
Never commit this file. The MAKEEMWAIT_API_KEY variable is only accessible server-side because it does not have the NEXT_PUBLIC_ prefix. This means it is available in Server Components, Route Handlers, and Server Actions, but not in client-side code.
You can find your API key in the MakeEmWait dashboard under your account settings. The key is shown only once when generated, so store it somewhere safe.