Next.js 8升级至11后使用next/script加载Google reCAPTCHA失效问题排查
我最近帮好几个开发者处理过Next.js从旧版本升级到11后,用next/script加载第三方脚本踩的坑,你遇到的reCAPTCHA和Moengage加载失败的情况,其实核心问题都是next/script的加载策略和传统script标签的执行时机、全局变量暴露逻辑不一样导致的。下面给你针对性的解决方案:
一、解决 reCAPTCHA 无法加载的问题
你用strategy="beforeInteractive"时,onloadCallback这个全局回调大概率还没被注册——因为beforeInteractive脚本是在Next.js核心代码加载前执行的,而你的回调函数通常是在组件或客户端代码里定义的,这时候根本不存在。
方案1:提前注册全局回调(适合全局使用reCAPTCHA的场景)
把onloadCallback定义在_document.js的<Head>里,用同步脚本提前注册,再加载reCAPTCHA:
// pages/_document.js import { Html, Head, Main, NextScript } from 'next/document' import Script from 'next/script' export default function Document() { return ( <Html> <Head> {/* 提前注册全局回调函数 */} <Script strategy="beforeInteractive" id="recaptcha-init"> {` window.onloadCallback = function() { // 这里根据你的react-recaptcha需求初始化 if (window.grecaptcha) { // 比如渲染指定元素的验证码 window.grecaptcha.render('recaptcha-container', { sitekey: '你的站点密钥', callback: (response) => { // 处理验证成功后的逻辑 } }); } } `} </Script> {/* 加载reCAPTCHA脚本,此时回调已存在 */} <Script strategy="beforeInteractive" src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" /> </Head> <body> <Main /> <NextScript /> </body> </Html> ) }
方案2:组件内按需加载(适合局部使用reCAPTCHA的场景)
如果只在某个组件里用reCAPTCHA,改用afterInteractive策略,配合onLoad回调手动初始化,不用依赖全局onload参数:
import Script from 'next/script' import { useEffect, useRef } from 'react' export default function RecaptchaComponent() { const captchaRef = useRef(null) useEffect(() => { const initCaptcha = () => { if (window.grecaptcha) { window.grecaptcha.render(captchaRef.current, { sitekey: '你的站点密钥', // 其他配置项 }) } } // 脚本已加载则直接初始化,否则监听加载完成事件 if (window.grecaptcha) { initCaptcha() } else { document.addEventListener('captchaLoaded', initCaptcha) } return () => document.removeEventListener('captchaLoaded', initCaptcha) }, []) return ( <> <div ref={captchaRef} id="recaptcha-container" /> <Script strategy="afterInteractive" src="https://www.google.com/recaptcha/api.js" onLoad={() => document.dispatchEvent(new Event('captchaLoaded'))} /> </> ) }
二、解决 Moengage 无法加载的问题
Moengage依赖全局window.moengage变量,next/script的加载策略可能导致初始化代码访问不到这个变量。推荐用以下方式处理:
方案:脚本加载完成后再初始化
在使用Moengage的页面或组件中,用lazyOnload(或afterInteractive)加载脚本,然后在onLoad回调里执行初始化:
import Script from 'next/script' export default function HomePage() { return ( <> {/* 页面内容 */} <Script strategy="lazyOnload" src="https://cdn.moengage.com/webpush/moe_webSdk.min.latest.js" onLoad={() => { if (window.moengage) { window.moengage = moengage.initialize({ app_id: '你的Moengage App ID', // 其他配置参数 }); } }} /> </> ) }
如果需要Moengage更早启动,把strategy换成afterInteractive即可,核心是确保初始化代码在脚本加载完成后执行。
三、给 Razor Pay 测试的建议
测试Razor Pay时,可以参考上面的思路:
- 先用
afterInteractive或lazyOnload加载它的脚本; - 在
onLoad回调里执行初始化逻辑,避免直接依赖全局变量未定义的情况; - 如果遇到问题,检查是否有全局回调需要提前注册,或者是否需要调整加载策略。
内容的提问来源于stack exchange,提问作者anup

