🚀. Socket Launch Week Day 2:Introducing Manifest Alerts.Learn more
Sign In

@silverassist/recaptcha

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@silverassist/recaptcha

Google reCAPTCHA v3 integration for Next.js applications with Server Actions support

latest
Source
npmnpm
Version
0.2.1
Version published
Maintainers
1
Created
Source

@silverassist/recaptcha

Google reCAPTCHA v3 integration for Next.js applications with Server Actions support.

npm version License

Features

  • Client Component: RecaptchaWrapper for automatic token generation
  • Server Validation: validateRecaptcha function for Server Actions
  • TypeScript Support: Full type definitions included
  • Next.js Optimized: Works with App Router and Server Actions
  • Auto Token Refresh: Tokens refresh automatically before expiration
  • Graceful Degradation: Works in development without credentials
  • Configurable Thresholds: Custom score thresholds per form
  • Lazy Loading: Optional lazy loading for better performance
  • Singleton Script Loading: Prevents duplicate script loads across multiple forms

Installation

npm install @silverassist/recaptcha
# or
yarn add @silverassist/recaptcha
# or
pnpm add @silverassist/recaptcha

Setup

1. Get reCAPTCHA Keys

  • Go to Google reCAPTCHA Admin
  • Create a new site with reCAPTCHA v3
  • Get your Site Key (public) and Secret Key (private)

2. Add Environment Variables

# .env.local
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=your_site_key_here
RECAPTCHA_SECRET_KEY=your_secret_key_here

Usage

Client Component

Add RecaptchaWrapper inside your form:

"use client";

import { RecaptchaWrapper } from "@silverassist/recaptcha";

export function ContactForm() {
  return (
    <form action={submitForm}>
      <RecaptchaWrapper action="contact_form" />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </form>
  );
}

Server Action

Validate the token in your Server Action:

"use server";

import { validateRecaptcha, getRecaptchaToken } from "@silverassist/recaptcha/server";

export async function submitForm(formData: FormData) {
  // Get and validate reCAPTCHA token
  const token = getRecaptchaToken(formData);
  const recaptcha = await validateRecaptcha(token, "contact_form");

  if (!recaptcha.success) {
    return { success: false, message: recaptcha.error };
  }

  // Process form data...
  const email = formData.get("email");
  const message = formData.get("message");

  // Your form processing logic here

  return { success: true };
}

⚠️ Important: Custom FormData

RecaptchaWrapper injects a hidden input field containing the reCAPTCHA token. If your form handler creates a custom FormData object, you must ensure the hidden token is included.

❌ This will fail (token is missing):

"use client";

import { RecaptchaWrapper } from "@silverassist/recaptcha";
import { submitForm } from "./actions"; // Your server action

export function ContactForm() {
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    
    // ❌ Creating empty FormData - hidden reCAPTCHA input is NOT included!
    const formData = new FormData();
    formData.set("email", "user@example.com");
    formData.set("message", "Hello");
    
    await submitForm(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <RecaptchaWrapper action="contact_form" />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </form>
  );
}
"use client";

import { RecaptchaWrapper } from "@silverassist/recaptcha";
import { submitForm } from "./actions"; // Your server action

export function ContactForm() {
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    
    // ✅ Pass form element - captures ALL inputs including hidden reCAPTCHA token
    const formData = new FormData(e.currentTarget);
    
    await submitForm(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <RecaptchaWrapper action="contact_form" />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </form>
  );
}

✅ Alternative: Start with form element, then modify

"use client";

import { RecaptchaWrapper } from "@silverassist/recaptcha";
import { submitForm } from "./actions"; // Your server action

export function ContactForm() {
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    
    // ✅ Start with form element (includes hidden token)
    const formData = new FormData(e.currentTarget);
    
    // Then add/override specific fields
    formData.set("customField", "customValue");
    formData.set("timestamp", Date.now().toString());
    
    await submitForm(formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <RecaptchaWrapper action="contact_form" />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </form>
  );
}

Performance Optimization: Lazy Loading

The lazy prop enables lazy loading of the reCAPTCHA script, which defers loading until the form becomes visible in the viewport. This significantly improves initial page load performance.

Performance Benefits

Note: These metrics are approximate values measured on production websites using Google reCAPTCHA v3. Actual performance improvements will vary based on network conditions, device capabilities, and page complexity.

MetricWithout Lazy LoadingWith Lazy LoadingImprovement
Initial JS320KB+0 KB (until visible)-320KB
TBT (Total Blocking Time)~470ms~0ms (deferred)-470ms
TTI (Time to Interactive)+2-3sMinimal impact-2-3s

Basic Lazy Loading

Enable lazy loading by adding the lazy prop:

"use client";

import { RecaptchaWrapper } from "@silverassist/recaptcha";

export function ContactForm() {
  return (
    <form action={submitForm}>
      {/* Script loads only when form is near viewport */}
      <RecaptchaWrapper action="contact_form" lazy />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </form>
  );
}

Custom Root Margin

Control when the script loads with the lazyRootMargin prop (default: "200px"):

// Load script earlier (400px before entering viewport)
<RecaptchaWrapper action="contact_form" lazy lazyRootMargin="400px" />

// Load script later (load only when fully visible)
<RecaptchaWrapper action="contact_form" lazy lazyRootMargin="0px" />

// Load with negative margin (load only after scrolling past)
<RecaptchaWrapper action="contact_form" lazy lazyRootMargin="-100px" />

Best Practices

1. Use lazy loading for below-the-fold forms

// Hero form (above the fold) - load immediately
<RecaptchaWrapper action="hero_signup" />

// Footer form (below the fold) - lazy load
<RecaptchaWrapper action="footer_contact" lazy />

2. Multiple forms on the same page

The package automatically uses singleton script loading, so the script is only loaded once even with multiple forms:

export function MultiFormPage() {
  return (
    <>
      {/* First form triggers script load */}
      <RecaptchaWrapper action="newsletter" lazy />
      
      {/* Second form reuses the same script */}
      <RecaptchaWrapper action="contact" lazy />
      
      {/* Third form also reuses the script */}
      <RecaptchaWrapper action="feedback" lazy />
    </>
  );
}

3. Adjust root margin based on form position

// Form near top - smaller margin for faster load
<RecaptchaWrapper action="signup" lazy lazyRootMargin="100px" />

// Form far down page - larger margin to load in advance
<RecaptchaWrapper action="newsletter" lazy lazyRootMargin="500px" />

API Reference

RecaptchaWrapper

Client component that loads reCAPTCHA and generates tokens.

<RecaptchaWrapper
  action="contact_form"      // Required: action name for analytics
  inputName="recaptchaToken" // Optional: hidden input name (default: "recaptchaToken")
  inputId="recaptcha-token"  // Optional: hidden input id
  siteKey="..."              // Optional: override env variable
  refreshInterval={90000}    // Optional: token refresh interval in ms (default: 90000)
  onTokenGenerated={(token) => {}} // Optional: callback when token is generated
  onError={(error) => {}}    // Optional: callback on error
  lazy={false}               // Optional: enable lazy loading (default: false)
  lazyRootMargin="200px"     // Optional: IntersectionObserver rootMargin (default: "200px")
/>

Props

PropTypeDefaultDescription
actionstringRequiredAction name for reCAPTCHA analytics (e.g., "contact_form", "signup")
inputNamestring"recaptchaToken"Name attribute for the hidden input field
inputIdstring"recaptcha-token"ID attribute for the hidden input field
siteKeystringprocess.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEYOverride the site key from environment variable
refreshIntervalnumber90000Token refresh interval in milliseconds (90 seconds)
onTokenGenerated(token: string) => voidundefinedCallback invoked when a new token is generated
onError(error: Error) => voidundefinedCallback invoked when an error occurs
lazybooleanfalseEnable lazy loading to defer script until form is visible
lazyRootMarginstring"200px"IntersectionObserver rootMargin (used when lazy is true)

validateRecaptcha

Server-side token validation function.

const result = await validateRecaptcha(
  token,          // Token from form
  "contact_form", // Expected action (optional)
  {
    scoreThreshold: 0.5, // Minimum score (default: 0.5)
    secretKey: "...",    // Override env variable
    debug: true,         // Enable debug logging
  }
);

// Result type:
// {
//   success: boolean,
//   score: number,
//   error?: string,
//   skipped?: boolean,
//   rawResponse?: RecaptchaVerifyResponse
// }

isRecaptchaEnabled

Check if reCAPTCHA is configured.

import { isRecaptchaEnabled } from "@silverassist/recaptcha/server";

if (isRecaptchaEnabled()) {
  // Validate token
} else {
  // Skip validation (development)
}

getRecaptchaToken

Extract token from FormData.

import { getRecaptchaToken } from "@silverassist/recaptcha/server";

const token = getRecaptchaToken(formData);
const token = getRecaptchaToken(formData, "customFieldName");

Score Thresholds

reCAPTCHA v3 returns a score from 0.0 to 1.0:

ScoreMeaning
1.0Very likely human
0.7+Likely human
0.5Default threshold
0.3-Suspicious
0.0Very likely bot

Adjust threshold based on form sensitivity:

// Standard forms
await validateRecaptcha(token, "contact", { scoreThreshold: 0.5 });

// Sensitive forms (payments, account creation)
await validateRecaptcha(token, "payment", { scoreThreshold: 0.7 });

// Low-risk forms (newsletter signup)
await validateRecaptcha(token, "newsletter", { scoreThreshold: 0.3 });

Subpath Imports

You can import from specific subpaths for better tree-shaking:

// Main exports (client + server + types)
import { RecaptchaWrapper, validateRecaptcha } from "@silverassist/recaptcha";

// Client only
import { RecaptchaWrapper } from "@silverassist/recaptcha/client";

// Server only
import { validateRecaptcha, getRecaptchaToken, isRecaptchaEnabled } from "@silverassist/recaptcha/server";

// Types only
import type { RecaptchaValidationResult, RecaptchaWrapperProps } from "@silverassist/recaptcha/types";

// Constants only
import { DEFAULT_SCORE_THRESHOLD, RECAPTCHA_CONFIG } from "@silverassist/recaptcha/constants";

Development

In development, when RECAPTCHA_SECRET_KEY is not set, validation is skipped and forms work normally. This allows testing without reCAPTCHA credentials.

const result = await validateRecaptcha(token, "test");
// Returns: { success: true, score: 1, skipped: true }

TypeScript

Full TypeScript support with exported types:

import type {
  RecaptchaWrapperProps,
  RecaptchaValidationResult,
  RecaptchaVerifyResponse,
  RecaptchaConfig,
  RecaptchaValidationOptions,
} from "@silverassist/recaptcha";

License

Polyform Noncommercial License 1.0.0

Keywords

recaptcha

FAQs

Package last updated on 06 Feb 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts