You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Stripe Elements预渲染报错:本地正常Vercel CI/CD编译失败

解决Vercel预渲染Stripe CheckoutForm时的Elements上下文报错

问题原因

本地运行正常是因为组件在客户端渲染,而Vercel的预渲染(SSG/SSR)会在服务器端执行组件代码。Stripe的useStripe()和useElements()钩子必须在客户端环境且被<Elements> provider包裹的上下文里运行,服务器端执行时无法获取Elements上下文,因此触发报错。

解决方案

1. 强制CheckoutForm仅在客户端渲染

使用Next.js的dynamic导入并禁用SSR,确保组件只在浏览器环境中加载:

// index.js
import dynamic from 'next/dynamic';
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';

// 禁用SSR,仅客户端渲染CheckoutForm
const CheckoutForm = dynamic(() => import('./CheckoutForm'), { ssr: false });

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);

export default function BillingPage() {
  // 生成或获取clientSecret的逻辑(建议在客户端请求)
  const options = {
    clientSecret: '你的支付意图clientSecret',
  };

  return (
    <Elements stripe={stripePromise} options={options}>
      <CheckoutForm />
    </Elements>
  );
}

2. 确保Elements provider的条件渲染仅在客户端触发

如果是根据clientSecret的存在与否渲染<Elements>,要在客户端判断状态,避免服务器端渲染时组件暴露在Elements上下文之外:

// index.js
import { useState, useEffect } from 'react';
import dynamic from 'next/dynamic';
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';

const CheckoutForm = dynamic(() => import('./CheckoutForm'), { ssr: false });
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);

export default function BillingPage() {
  const [clientSecret, setClientSecret] = useState('');
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    // 仅在客户端请求获取clientSecret
    async function fetchPaymentIntent() {
      const res = await fetch('/api/create-payment-intent');
      const data = await res.json();
      setClientSecret(data.clientSecret);
      setIsReady(true);
    }
    fetchPaymentIntent();
  }, []);

  const options = { clientSecret };

  return (
    <>
      {isReady ? (
        <Elements stripe={stripePromise} options={options}>
          <CheckoutForm />
        </Elements>
      ) : (
        <div>加载支付组件中...</div>
      )}
    </>
  );
}

3. 检查CheckoutForm内部逻辑

确保CheckoutForm中没有在服务器端执行的代码调用useStripe()或useElements(),由于已经通过dynamic禁用了SSR,组件只会在客户端渲染,自然能获取到Elements上下文:

// CheckoutForm.js
import { useStripe, useElements } from '@stripe/react-stripe-js';
import { CardElement } from '@stripe/react-stripe-js';

export default function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!stripe || !elements) return;

    // 支付逻辑
    const { error } = await stripe.confirmCardPayment(clientSecret, {
      payment_method: {
        card: elements.getElement(CardElement),
      },
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <CardElement />
      <button type="submit" disabled={!stripe}>支付</button>
    </form>
  );
}

为什么之前的包裹方法无效

即使在index.js中用<Elements>包裹了CheckoutForm,预渲染时服务器端仍会执行CheckoutForm的代码,而Stripe的客户端库在服务器端无法初始化Elements上下文。必须通过禁用SSR让组件仅在客户端运行,才能确保钩子能获取到正确的上下文。

内容的提问来源于stack exchange,提问作者Esteban Vargas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 18:15:51