NextJS集成Adyen Dropin报window未定义、AdyenCheckout非构造函数求助
Next.js 集成Adyen Dropin 常见报错原因和正确实现
报错根源
window is not defined:Adyen Web是纯浏览器端SDK,顶层静态导入时会在SSR(服务端渲染)阶段执行代码,此时Node环境不存在window对象,直接触发报错。TypeError: AdyenCheckout is not a constructor:- 尝试用Next.js的
dynamic()加载SDK的用法存在本质错误:dynamic()的设计目标是代码分割加载React组件,返回值是可被React树渲染的组件包装,不是SDK本身导出的构造函数。就算设置ssr:false,拿到的依然是React组件,用new调用自然会触发报错。 - 直接在组件渲染阶段执行
import('@adyen/adyen-web').default是同步取值,import()返回的是未解析的Promise对象,根本没等到SDK加载完成,拿到的自然是无法作为构造函数的Promise值。 @adyen/adyen-web的AdyenCheckout是具名导出,不是默认导出,就算等待Promise resolve后,错误取.default属性也拿不到正确的构造函数。- 原代码存在ref绑定错误:把Adyen组件实例作为ref绑定到DOM上,和实际要挂载的容器DOM混淆了。
- 尝试用Next.js的
正确实现步骤
- 保留顶层导入Adyen的CSS文件,纯样式导入没有副作用,不会触发
window报错。 - 所有涉及SDK加载、初始化的逻辑全部放到
useEffect内部执行:useEffect的触发时机是浏览器端DOM挂载完成后,此时window对象存在,完全规避SSR环境的报错。 - 等支付方法接口的返回结果、DOM容器实际渲染完成后,再异步导入Adyen SDK,完成初始化和挂载。
- 客户端可访问的Adyen clientKey需要使用
NEXT_PUBLIC_前缀的环境变量,否则在浏览器端拿不到值。
修正后的完整代码
import Head from 'next/head'; import { useRef, useEffect, useState } from 'react'; import { callServer, handleSubmission } from '../util/serverHelpers'; import '@adyen/adyen-web/dist/adyen.css'; export default function Dropin() { // 存放挂载Dropin的真实DOM容器 const dropinContainerRef = useRef(null); // 存储从服务端获取的支付方法列表 const [paymentMethods, setPaymentMethods] = useState(null); // 页面挂载完成后拉取支付方法数据 useEffect(() => { const fetchPaymentMethods = async () => { const res = await callServer(`${process.env.BASE_URL}/api/getPaymentMethods`); setPaymentMethods(res); }; fetchPaymentMethods(); }, []); // 数据和DOM就绪后初始化Adyen Dropin useEffect(() => { // 前置校验:支付方法没拿到、DOM容器不存在时直接跳过 if (!paymentMethods || !dropinContainerRef.current) return; const initCheckout = async () => { // 浏览器端异步加载SDK,拿到具名导出的AdyenCheckout构造函数 const { AdyenCheckout } = await import('@adyen/adyen-web'); const configuration = { paymentMethodsResponse: paymentMethods, clientKey: process.env.NEXT_PUBLIC_ADYEN_CLIENT_KEY, locale: "en_AU", environment: "test", paymentMethodsConfiguration: { card: { showPayButton: true, hasHolderName: true, holderNameRequired: true, name: "Credit or debit card", amount: { value: 2000, currency: "AUD" } } }, onSubmit: (state, component) => { if (state.isValid) { handleSubmission(state, component, "/api/initiatePayment"); } }, onAdditionalDetails: (state, component) => { handleSubmission(state, component, "/api/submitAdditionalDetails"); }, }; // 初始化实例,创建Dropin并挂载到真实DOM容器 const checkoutInstance = new AdyenCheckout(configuration); checkoutInstance.create('dropin').mount(dropinContainerRef.current); }; initCheckout(); }, [paymentMethods]); return ( <div> <Head> <title>Dropin支付</title> </Head> {/* 绑定DOM容器ref */} <div ref={dropinContainerRef}></div> </div> ); }
额外注意事项
- 不要在组件渲染阶段直接执行SDK初始化逻辑,渲染逻辑会同时在服务端和客户端执行,必然触发
window is not defined。 - 若后续需要卸载Dropin,可在组件的清理函数(
useEffect的return回调)中调用dropin.unmount(),避免内存泄漏。
内容的提问来源于stack exchange,提问作者Darragh
相关产品推荐
相关产品推荐

