Fix Turnstile API initialization race

This commit is contained in:
2026-09-18 01:12:39 +02:00
parent caa24905ec
commit b29862b4b7
2 changed files with 155 additions and 54 deletions
+38 -54
View File
@@ -1,28 +1,7 @@
"use client";
import Script from "next/script";
import { useCallback, useEffect, useRef, useState } from "react";
type TurnstileWidgetId = string;
type TurnstileApi = {
render: (
container: HTMLElement,
options: {
sitekey: string;
action: string;
callback: (token: string) => void;
"expired-callback": () => void;
"error-callback": () => void;
},
) => TurnstileWidgetId;
reset: (widgetId: TurnstileWidgetId) => void;
};
declare global {
interface Window {
turnstile?: TurnstileApi;
}
}
import { useEffect, useRef, useState } from "react";
import { loadTurnstile, type TurnstileWidgetId } from "@/lib/turnstile/client";
type TurnstileWidgetProps = {
siteKey: string;
@@ -50,45 +29,50 @@ export function TurnstileWidget({ siteKey, action, resetKey, onTokenChange }: Tu
onTokenChangeRef.current(null);
}, [resetKey]);
const render = useCallback(() => {
useEffect(() => {
if (!siteKey || !containerRef.current || widgetIdRef.current !== null) return;
if (!window.turnstile) {
setLoadError(true);
return;
}
try {
widgetIdRef.current = window.turnstile.render(containerRef.current, {
sitekey: siteKey,
action,
callback: (token) => {
setVerificationStatus("ready");
onTokenChangeRef.current(token);
},
"expired-callback": () => {
setVerificationStatus("expired");
onTokenChangeRef.current(null);
},
"error-callback": () => {
onTokenChangeRef.current(null);
let cancelled = false;
loadTurnstile()
.then((turnstile) => {
if (cancelled || !containerRef.current || widgetIdRef.current !== null) return;
try {
widgetIdRef.current = turnstile.render(containerRef.current, {
sitekey: siteKey,
action,
callback: (token) => {
setVerificationStatus("ready");
onTokenChangeRef.current(token);
},
"expired-callback": () => {
setVerificationStatus("expired");
onTokenChangeRef.current(null);
},
"error-callback": () => {
onTokenChangeRef.current(null);
setLoadError(true);
},
});
} catch {
setLoadError(true);
},
onTokenChangeRef.current(null);
}
})
.catch(() => {
if (cancelled) return;
setLoadError(true);
onTokenChangeRef.current(null);
});
} catch {
setLoadError(true);
onTokenChangeRef.current(null);
}
return () => {
cancelled = true;
};
}, [action, siteKey]);
return (
<div className="space-y-2" aria-live="polite">
<Script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
strategy="afterInteractive"
onReady={render}
onError={() => setLoadError(true)}
/>
<div ref={containerRef} />
<div ref={containerRef} data-turnstile-container="true" />
{!siteKey || loadError ? (
<p className="text-sm text-red-700" role="alert">
Die Sicherheitsprüfung ist momentan nicht verfügbar. Bitte lade die Seite neu und versuche es erneut.