Skip to main content

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 included

Installation

npm install @vouch-in/next

App Router (Next.js 13+)

Server Component

// 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

// 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

// 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

// 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

# .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:
// 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+)

// 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 };
}
// 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

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>;
}

Next Steps