4.0 KiB
turnstile
Cloudflare Turnstile: widget (client) + weryfikacja (server). Klucze z
SiteIntegrations (panel, nie env). Widget dostaje siteKey jako prop; verify
czyta secret server-side.
Config
Brak opcji — pola turnstileSiteKey / turnstileSecretKey są w
SiteIntegrations (tab Turnstile). Edytor wpisuje klucze w panelu.
Front — widget (client)
Import z @intecion/ipal-kit/client. siteKey pobierz server-side i przekaż jako prop:
// server component — pobiera publiczny siteKey z panelu
import { getSiteIntegrations } from '@intecion/ipal-kit'
const { turnstileSiteKey } = await getSiteIntegrations(payload)
// przekaż do swojego client-formularza → widget
'use client'
import { Turnstile } from '@intecion/ipal-kit/client'
import { useState } from 'react'
function ContactForm({ siteKey }) {
const [token, setToken] = useState<string | null>(null)
return (
<form>
{/* pola */}
<Turnstile siteKey={siteKey} onToken={setToken} theme="auto" />
<button disabled={!token}>Wyślij</button>
</form>
)
}
Widget ładuje skrypt Turnstile sam (bez next/script), zwraca token przez
onToken (null przy wygaśnięciu/błędzie).
Front — verify (server)
import { verifyTurnstile } from '@intecion/ipal-kit/server'
const ok = await verifyTurnstile({ token, payload, ip })
if (!ok) {
// odrzuć zgłoszenie
}
verifyTurnstile czyta secret z SiteIntegrations (Local API), woła Cloudflare.
Zwraca false na każdy problem (brak klucza, sieć, odrzucenie) — traktuj
false jako „nie ufaj temu zgłoszeniu". server-only gwarantuje, że nie
trafi do bundla przeglądarki.
W formularzach zwykle nie wołasz
verifyTurnstilewprost — robi tosubmitForm(patrz forms.md).
Uproszczone wpięcie — TurnstileProvider + useTurnstile (zalecane)
Jak CookieBanner: siteKey raz w layoutcie, formularze biorą z kontekstu. Koniec przekazywania siteKey do każdego formularza.
1. Provider w layoutcie (raz, siteKey z serwera)
// app/(frontend)/[locale]/layout.tsx (server)
import { TurnstileProvider } from '@intecion/ipal-kit/client'
import { getTurnstileSiteKey } from '@/lib/payload' // Twój helper server-side
export default async function Layout({ children }) {
const siteKey = await getTurnstileSiteKey() // z panelu (SiteIntegrations)
return (
<html>
<body>
<TurnstileProvider siteKey={siteKey}>
{children}
</TurnstileProvider>
</body>
</html>
)
}
2. Formularz — useTurnstile (zero plumbingu siteKey)
'use client'
import { useTurnstile } from '@intecion/ipal-kit/client'
function ContactForm() {
const { token, TurnstileWidget, reset, enabled } = useTurnstile()
async function handleSubmit(data) {
const result = await submitForm({ ...data, turnstileToken: token })
if (result.ok) reset() // wyczyść token na następne wysłanie
}
return (
<form onSubmit={...}>
{/* pola formularza */}
<TurnstileWidget /> {/* widget tam, gdzie ma być */}
<button type="submit">Wyślij</button>
</form>
)
}
token → do submitForm. TurnstileWidget → wstaw gdzie ma być. reset() → po
wysłaniu. enabled → false gdy brak klucza (dev bez Turnstile).
Dlaczego Turnstile NIE jest w pełni "wstaw i zapomnij" jak CookieBanner
CookieBanner jest samodzielny (renderuje się, zarządza zgodą, zero interakcji). Turnstile z natury jest CZĘŚCIĄ formularza — zwraca token, który formularz musi wysłać przy submit i zweryfikować server-side. Nie da się go „wstawić gdziekolwiek" — musi być w formularzu, przy jego logice wysyłki.
Provider+hook to maksimum uproszczenia: siteKey raz (jak CookieBanner), a w
formularzu tylko <TurnstileWidget/> + token. Reszta (weryfikacja) dzieje się
w submitForm automatycznie.
Stary sposób (nadal działa)
<Turnstile siteKey={...} onToken={...} /> bezpośrednio — jeśli potrzebujesz
pełnej kontroli albo masz nietypowy przypadek. Provider to warstwa wygody nad tym.