Setting Up Cloudflare Turnstile CAPTCHA in Nuxt 3
1. Get reCAPTCHA API Keys
Before integrating CAPTCHA into your Nuxt 3 project, you need API keys from Cloudflare Turnstile. Go to Cloudflare Turnstile CAPTCHA Admin, Register a new site. Then, Add your domain (for production) or localhost (for development). Copy the Site Key.
2. composables Create a new composables file composables/useGlobalRecaptcha.ts in your Nuxt 3 project:
// composables/useGlobalRecaptcha.ts
import { ref } from 'vue'
export const useTurnstile = (siteKey: string) => {
// Ensure a container element exists for Turnstile
const ensureTurnstileContainer = (): HTMLElement => {
// Check if an existing container exists
let container = document.getElementById('cf-turnstile-container')
// If not, create a hidden container
if (!container) {
container = document.createElement('div')
container.id = 'cf-turnstile-container'
container.style.display = 'none'
document.body.appendChild(container)
}
return container
}
// Load Turnstile script dynamically
const loadTurnstileScript = (): Promise<any> => {
return new Promise((resolve, reject) => {
// Check if script is already loaded
if (window.turnstile) {
resolve(window.turnstile)
return
}
const script = document.createElement('script')
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'
script.async = true
script.onload = () => {
if (window.turnstile) {
resolve(window.turnstile)
} else {
reject(new Error('Turnstile script failed to load'))
}
}
script.onerror = () => {
reject(new Error('Failed to load Turnstile script'))
}
document.head.appendChild(script)
})
}
// Generate token without rendering visible widget
const generateToken = async (): Promise<string> => {
try {
// Ensure script is loaded
await loadTurnstileScript()
// Create a container element
const container = ensureTurnstileContainer()
// Return a promise that resolves with the token
return new Promise((resolve, reject) => {
window.turnstile.render(container, {
sitekey: siteKey,
action: 'login',
callback: (token) => {
// Remove the container after token generation
container.remove()
resolve(token)
},
'error-callback': (error) => {
container.remove()
reject(error)
}
})
})
} catch (error) {
console.error('Token generation error:', error)
throw error
}
}
return {
generateToken
}
}
3. Use CAPTCHA in a Component
Example: Protecting a Form Submission
Edit your component (e.g., components/MyForm.vue):
<script setup>
const config = useRuntimeConfig();
const SITE_KEY = config.public.CAPTCHA_SITEKEY;
// Initialize Turnstile composable
const { generateToken } = useTurnstile(SITE_KEY);
const onSubmit = async () => {
// Generate token without visible challenge
const captcha_token = await generateToken();
const { data, error } = await useFetch('/api/verify-recaptcha', {
method: '',
body: {
captcha_token: captcha_token
}
});
if (error.value) {
console.error('reCAPTCHA validation failed:', error.value);
} else {
console.log('Form submitted successfully:', data.value);
}
};
</script>
<template>
<form @submit.prevent="onSubmit">
<button type="submit">Submit</button>
</form>
</template>
Deployment Considerations
Ensure your CAPTCHA keys are set correctly in your environment.
Use separate keys for development and production.
Monitor CAPTCHA scores to prevent false positives.