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.