Overview
The Vouch Next.js SDK provides seamless integration for both client and server components with automatic fingerprinting and optimized for Next.js App Router and Pages Router.Package:
@vouch-in/next
Next.js Version: 13+ (App Router) or 12+ (Pages Router)
TypeScript: Full type definitions includedInstallation
Copy
npm install @vouch-in/next
App Router (Next.js 13+)
Server Component
Copy
// app/api/validate/route.ts
import { Vouch } from '@vouch-in/next';
const vouch = new Vouch(
process.env.VOUCH_PROJECT_ID!,
process.env.VOUCH_SERVER_KEY!
);
export async function POST(request: Request) {
const { email, fingerprintHash } = await request.json();
const result = await vouch.validate(email, {
ip: request.headers.get('x-forwarded-for') || undefined,
userAgent: request.headers.get('user-agent') || undefined,
fingerprintHash, // Optional: from client SDK
});
// Check the recommendation
if (result.recommendation !== 'allow') {
return Response.json({ error: 'Email validation failed' }, { status: 400 });
}
return Response.json({ success: true, recommendation: result.recommendation });
}
Client Component
Copy
// app/signup/page.tsx
'use client';
import { useState } from 'react';
import { VouchProvider, useValidateEmail } from '@vouch-in/next/client';
export default function SignupPage() {
return (
<VouchProvider
projectId={process.env.NEXT_PUBLIC_VOUCH_PROJECT_ID!}
apiKey={process.env.NEXT_PUBLIC_VOUCH_CLIENT_KEY!}
>
<SignupForm />
</VouchProvider>
);
}
function SignupForm() {
const { validate, loading, data } = useValidateEmail();
const [email, setEmail] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
const result = await validate(email);
if (result.recommendation === 'allow') {
// Proceed with signup
console.log('Email is valid!');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button disabled={loading}>
{loading ? 'Validating...' : 'Sign Up'}
</button>
{data && data.recommendation !== 'allow' && (
<p className="error">Email validation failed</p>
)}
</form>
);
}
Pages Router (Next.js 12)
API Route
Copy
// pages/api/validate.ts
import { Vouch } from '@vouch-in/next';
import type { NextApiRequest, NextApiResponse } from 'next';
const vouch = new Vouch(
process.env.VOUCH_PROJECT_ID!,
process.env.VOUCH_SERVER_KEY!
);
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { email, fingerprintHash } = req.body;
const result = await vouch.validate(email, {
ip: req.socket.remoteAddress,
userAgent: req.headers['user-agent'],
fingerprintHash,
});
if (result.recommendation !== 'allow') {
return res.status(400).json({
error: 'Email validation failed',
recommendation: result.recommendation
});
}
res.status(200).json({ success: true, recommendation: result.recommendation });
}
Client Page
Copy
// pages/signup.tsx
import { useState } from 'react';
import { VouchProvider, useValidateEmail } from '@vouch-in/next/client';
export default function SignupPage() {
return (
<VouchProvider
projectId={process.env.NEXT_PUBLIC_VOUCH_PROJECT_ID!}
apiKey={process.env.NEXT_PUBLIC_VOUCH_CLIENT_KEY!}
>
<SignupForm />
</VouchProvider>
);
}
function SignupForm() {
const { validate, loading, data } = useValidateEmail();
const [email, setEmail] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
const result = await validate(email);
if (result.recommendation === 'allow') {
// All checks passed
await submitSignup(email);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<button disabled={loading}>
{loading ? 'Validating...' : 'Sign Up'}
</button>
</form>
);
}
Environment Variables
Copy
# .env.local
VOUCH_PROJECT_ID=your_project_id
VOUCH_SERVER_KEY=your_server_key
NEXT_PUBLIC_VOUCH_PROJECT_ID=your_project_id
NEXT_PUBLIC_VOUCH_CLIENT_KEY=your_client_key
Use
NEXT_PUBLIC_ prefix only for client keys. Never expose server keys!Middleware
Validate emails in Next.js middleware:Copy
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { Vouch } from '@vouch-in/next';
const vouch = new Vouch(
process.env.VOUCH_PROJECT_ID!,
process.env.VOUCH_SERVER_KEY!
);
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname === '/api/signup') {
const body = await request.json();
const { email } = body;
const result = await vouch.validate(email, {
ip: request.ip,
userAgent: request.headers.get('user-agent') || undefined,
});
if (result.recommendation === 'block') {
return NextResponse.json(
{ error: 'Email blocked' },
{ status: 400 }
);
}
if (!result.checks.syntax?.pass) {
return NextResponse.json(
{ error: 'Invalid email format' },
{ status: 400 }
);
}
}
return NextResponse.next();
}
Server Actions (Next.js 14+)
Copy
// app/actions.ts
'use server';
import { Vouch } from '@vouch-in/next';
const vouch = new Vouch(
process.env.VOUCH_PROJECT_ID!,
process.env.VOUCH_SERVER_KEY!
);
export async function validateEmail(email: string) {
const result = await vouch.validate(email);
if (result.recommendation !== 'allow') {
return { success: false, error: 'Email validation failed' };
}
if (!result.checks.disposable?.pass) {
return { success: false, error: 'Disposable emails not allowed' };
}
return { success: true, metadata: result.metadata };
}
Copy
// app/signup/page.tsx
'use client';
import { validateEmail } from './actions';
export default function SignupForm() {
async function handleSubmit(formData: FormData) {
const email = formData.get('email') as string;
const result = await validateEmail(email);
if (!result.success) {
alert(result.error);
return;
}
// Proceed with signup
}
return (
<form action={handleSubmit}>
<input type="email" name="email" required />
<button type="submit">Sign Up</button>
</form>
);
}
API Types
Copy
interface ValidationResponse {
checks: Record<string, CheckResult>;
metadata: {
fingerprintHash: string | null;
previousSignups: number;
totalLatency: number;
};
recommendation: 'allow' | 'block' | 'flag';
signals: string[];
}
interface CheckResult {
pass: boolean;
error?: string;
latency: number;
metadata?: Record<string, unknown>;
}