はじめに
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内で利用できるよう、設定します。
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で認証できたか、期限切れかを知りたいため、onVerified
とonExpired
の二つの関数を定義しています。
(エラー時のコールバックも作成可能です。詳しくはこちら)
<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>