8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

Nuxt.js(Vue.js)は便利なFWですが、ライブラリのサポートに難があり、過去動いていたライブラリが動かないことも結構多いです。

特に、reCAPTCHA v2のサポート状況は厳しく、下記のような有様です。

vue-recaptcha

v3はまだアルファ版でうまく動かないし、かといって最後の安定版であるv2.0.3はVue最新版でnpm installするとコンフリクトエラー発生
(yarnだと依存関係が解消できて利用できるのですが、npmで利用できないライブラリを無理やり入れたくない)

nuxt-community/recaptcha-module

nuxtコミュニティから提供されている公式モジュールですが、
2023年の3月から割と重大なバグが放置されています。
諦めてReact使えってことですかね。

なので自分で実装して解決しました。

実装手順

①:環境変数設定

reCAPTCHAのサイトキーをNuxt.js内で利用できるよう、設定します。

nuxt.config.ts
export default defineNuxtConfig({
~~~
    public: {
      recaptchaKey: "ここに.envファイルで定義したサイトキーの環境変数を入れる"
    }
~~~
})

①:プラグイン作成

plugin/recaptcha.client.tsを作成します。

元々はnuxt.config.tsでグローバルにhttps://www.google.com/recaptcha/api.jsを読み込んでいたのですが、
プラグインに変更したことで、必要な場所のみreCAPTCHAが読み込まれるようになりました。

import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin((nuxtApp) => {
  const loadRecaptcha = () => {
    return new Promise<void>((resolve) => {
      if (typeof window !== 'undefined' && window.grecaptcha) {
        resolve()
        return
      }

     const existingScript = document.querySelector('script[src^="https://www.google.com/recaptcha/api.js?render=explicit"]')
      if (existingScript) {
        existingScript.addEventListener('load', () => resolve())
      } else {
        const script = document.createElement('script')
        script.src = 'https://www.google.com/recaptcha/api.js?render=explicit'
        script.async = true
        script.defer = true
        script.onload = () => resolve()
        document.head.appendChild(script)
      }
    })
  }

  nuxtApp.provide('loadRecaptcha', loadRecaptcha)
})

ポイント

複数プロジェクトで利用していてもscriptタグが複数作られないよう、すでに存在しているかどうかを確認しています。
既に存在している場合もNuxtで利用できる状態でない可能性があるため、読み込みを検知し次第resolve()を実行しています。

②:コンポーネント作成

実際に画面上にreCAPTCHAを表示するためにコンポーネントを作成します。

今回はreCAPTCHAで認証できたか、期限切れかを知りたいため、onVerifiedonExpiredの二つの関数を定義しています。
(エラー時のコールバックも作成可能です。詳しくはこちら

recaptcha.vue
<template>
  <div>
    <div id="recaptcha" class="g-recaptcha" :data-sitekey="sitekey" />
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'

const { $loadRecaptcha } = useNuxtApp()
const config = useRuntimeConfig()
const sitekey = config.public.recaptchaKey
const isExpired = ref(false)

const emit = defineEmits<{
  (e: 'verified', response: string): void
  (e: 'expired'): void
}>()

const renderRecaptcha = () => {
  if ((window as any).grecaptcha && (window as any).grecaptcha.render) {
    try {
      (window as any).grecaptcha.render('recaptcha', {
        sitekey: sitekey,
        callback: onVerify,
        'expired-callback': onExpired
      })
    } catch (e) {
    }
  } else {
    setTimeout(renderRecaptcha, 100)
  }
}

const onVerify = (response: string) => {
  console.log('Verified:', response)
  isExpired.value = false
  emit('verified', response)
}

const onExpired = () => {
  console.log('reCAPTCHA expired')
  isExpired.value = true
  emit('expired')
}

onMounted(async () => {
  await $loadRecaptcha()
  renderRecaptcha()
})
</script>

できた!
スクリーンショット 2024-07-08 16.04.41.png

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?