← All components
reCAPTCHA Widget
Adds Google reCAPTCHA widget support to Next.js applications. This is a script-based integration (no npm install needed for the widget itself). The reCAPTCHA widget provides CAPTCHA protection for forms and prevents automated abuse. Requires a site key from Google reCAPTCHA admin console.
Copy the files below, or open this folder on GitHub. Each file is stamped with a limited-use licence, generator and date. SHA-256 is of the published catalog bytes, before the stamp.
- id
- recaptcha
- stack
- Next.js
- version
- 1.0.0
- compatibility
- nextjs >=16 <17 · react >=19 <20
- requires
- nextjs-base
- env vars
- NEXT_PUBLIC_RECAPTCHA_SITE_KEY
Verified passing · daily buildIntegrity sha256:700cbe52437b204e…
Files (2)
components/recaptcha-widget.tsx · sha256:700cbe52437b204e…
// Licensed by BotKelp — https://www.botkelp.com
'use client';
import { useEffect, useRef, useState } from 'react';
/**
* reCAPTCHA Widget component.
*
* This component renders a reCAPTCHA widget for forms.
* Supports both v2 (checkbox) and v3 (invisible) versions.
*
* Usage:
* import { ReCaptchaWidget } from '@/components/recaptcha-widget';
*
* function MyForm() {
* const [token, setToken] = useState('');
*
* return (
* <form>
* <ReCaptchaWidget
* siteKey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
* onVerify={(token) => setToken(token)}
* onExpire={() => setToken('')}
* />
* <input type="hidden" name="recaptchaToken" value={token} />
* </form>
* );
* }
*
* See: https://developers.google.com/recaptcha/docs/display
*/
export function ReCaptchaWidget({
siteKey,
version = 'v2',
action = '',
onVerify,
onExpire,
onError,
size = 'normal',
tabindex = 0,
className = '',
}: {
siteKey?: string;
version?: 'v2' | 'v3' | 'v2_invisible';
action?: string;
onVerify?: (token: string) => void;
onExpire?: () => void;
onError?: (error: Error) => void;
size?: 'normal' | 'compact';
tabindex?: number;
className?: string;
}) {
const resolvedSiteKey = siteKey || process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
const widgetRef = useRef<HTMLDivElement>(null);
const [widgetId, setWidgetId] = useState<number | null>(null);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
if (!resolvedSiteKey) {
console.warn('reCAPTCHA site key is not configured');
return;
}
// Check if grecaptcha is loaded
// @ts-ignore - grecaptcha is a global object
if (typeof window.grecaptcha === 'undefined') {
console.warn('reCAPTCHA script is not loaded');
return;
}
setIsLoaded(true);
if (version === 'v3') {
// For v3, we use the token directly
// @ts-ignore
const token = window.grecaptcha.getResponse();
if (token && onVerify) {
onVerify(token);
}
} else {
// For v2, render the widget
// @ts-ignore
const id = window.grecaptcha.render(widgetRef.current, {
sitekey: resolvedSiteKey,
size: size,
tabindex: tabindex,
callback: (token: string) => {
if (onVerify) onVerify(token);
},
'expired-callback': () => {
if (onExpire) onExpire();
},
'error-callback': (error: Error) => {
if (onError) onError(error);
},
});
setWidgetId(id);
return () => {
if (widgetId !== null) {
// @ts-ignore
window.grecaptcha.reset(widgetId);
}
};
}
}, [resolvedSiteKey, version, onVerify, onExpire, onError, size, tabindex]);
// Execute reCAPTCHA v3
const executeRecaptcha = async () => {
if (!isLoaded || !resolvedSiteKey) {
console.warn('reCAPTCHA is not ready');
return;
}
try {
// @ts-ignore
const token = await window.grecaptcha.execute(resolvedSiteKey, { action });
if (onVerify) onVerify(token);
return token;
} catch (error) {
if (onError) onError(error as Error);
console.error('reCAPTCHA execution failed:', error);
return null;
}
};
// Reset reCAPTCHA
const resetRecaptcha = () => {
if (widgetId !== null) {
// @ts-ignore
window.grecaptcha.reset(widgetId);
}
if (onExpire) onExpire();
};
// Expose execute and reset methods
// @ts-ignore - Adding custom methods to ref
if (widgetRef.current) {
// @ts-ignore
widgetRef.current.execute = executeRecaptcha;
// @ts-ignore
widgetRef.current.reset = resetRecaptcha;
}
if (version === 'v3' || version === 'v2_invisible') {
return null; // Invisible reCAPTCHA
}
return (
<div
ref={widgetRef}
className={`g-recaptcha ${className}`}
data-sitekey={resolvedSiteKey}
data-size={size}
data-tabindex={tabindex}
/>
);
}
export default ReCaptchaWidget;