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

Next.js 14中Zod解析环境变量报错(仅客户端组件触发)

Next.js 14 + Zod 环境变量解析错误问题

错误信息

ZodError: [
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "undefined",
    "path": [
      "API_BASE_URL"
    ],
    "message": "Required"
  },
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "undefined",
    "path": [
      "STRIPE_SECRET_KEY"
    ],
    "message": "Required"
  }
]

错误触发位置:

// lib/zod_schemas/serverEnvSchema.ts (8:29) @ parse
   6 | });
   7 |
>  8 | export const env = envSchema.parse({
     |                           ^
   9 | API_BASE_URL: process.env.API_BASE_URL,
  10 | STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
  11 | });

触发组件(CheckoutButton)

注释该组件后错误消失:

"use client";

import { loadStripe } from "@stripe/stripe-js";
import { useRouter } from "next/navigation";

import { useGlobalContext } from "@/components/context/GlobalContext";

import checkout from "@/lib/checkout";

// import { env } from "@/lib/zod_schemas/clientEnvSchema";
import { Button } from "@/components/ui/button";

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

const CheckoutButton = () => {
  const { cartProducts } = useGlobalContext();
  const router = useRouter();

  const handleCheckout = async () => {
    try {
      const stripe = await stripePromise;
      // checkout 是单独文件中创建的 "use server" 服务端动作函数,会用到环境变量
      const { sessionId } = await checkout(cartProducts);

      const { error } = await stripe?.redirectToCheckout({
        sessionId,
      });

      if (error) {
        router.push("/error");
      }
    } catch (err) {
      console.error("Error in creating checkout session:", err);
      router.push("/error");
    }
  };

  return (
    <Button
      className="mt-6 w-full rounded-md bg-blue-500 py-1.5 font-medium text-blue-50 hover:bg-blue-600"
      onClick={handleCheckout}
    >
      Buy Now
    </Button>
  );
};

export default CheckoutButton;

环境变量文件(.env.local)

API_BASE_URL=someurl
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=somekey
STRIPE_SECRET_KEY=somekey

Schema 文件

serverEnvSchema.ts

import zod from "zod";

const envSchema = zod.object({
  API_BASE_URL: zod.string().min(1),
  STRIPE_SECRET_KEY: zod.string().min(1),
});

export const env = envSchema.parse({
  API_BASE_URL: process.env.API_BASE_URL,
  STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
});

clientEnvSchema.ts

import zod from "zod";

const envSchema = zod.object({
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: zod.string().min(1),
});

export const env = envSchema.parse({
  NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:
    process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
});

背景与尝试过的方案

此前仅配置API_BASE_URL时一切正常,添加两个Stripe变量后出现错误。

尝试过的方案:

  • 将环境变量拆分为服务端、客户端两个独立Schema文件(之前所有变量放一个文件,问题依旧)
  • 两种方式调用客户端公开环境变量:
    1. 通过Zod解析的clientEnvSchema:
      import { env } from "@/lib/zod_schemas/clientEnvSchema";
      
      const stripePromise = loadStripe(
        env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
      );
      
    2. 直接使用process.env:
      // import { env } from "@/lib/zod_schemas/clientEnvSchema";
      
      const stripePromise = loadStripe(
        process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
      );
      

两种方式均无法解决问题。

补充说明

该Zod错误仅在标记为"use client"的客户端组件中出现,服务端组件中可以正常打印变量值且无错误。


解决方案

核心原因

客户端组件在浏览器运行时,未加NEXT_PUBLIC_前缀的服务端专属变量(如API_BASE_URL、STRIPE_SECRET_KEY)不会被注入客户端。如果serverEnvSchema.ts被客户端组件间接引入(比如通过服务端动作checkout的依赖链),就会导致客户端执行parse()时这些变量为undefined,触发Zod校验错误。

修复步骤

  1. 限制服务端Schema仅在服务端解析
    安装server-only包(强制模块仅在服务端加载,避免被客户端打包):

    npm install server-only
    

    修改serverEnvSchema.ts:

    import "server-only";
    import zod from "zod";
    
    const envSchema = zod.object({
      API_BASE_URL: zod.string().min(1),
      STRIPE_SECRET_KEY: zod.string().min(1),
    });
    
    export const env = envSchema.parse({
      API_BASE_URL: process.env.API_BASE_URL,
      STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
    });
    
  2. 排查依赖链,避免客户端引入服务端模块
    检查checkout服务端动作的代码,确保它引入的serverEnvSchema不会被客户端组件间接加载。服务端动作本身由Next.js处理为服务端执行,但其依赖的模块必须保证是服务端专属的。

  3. 客户端仅使用客户端Schema
    客户端组件(如CheckoutButton)只允许引入clientEnvSchema.ts,通过它获取客户端变量:

    import { env } from "@/lib/zod_schemas/clientEnvSchema";
    
    const stripePromise = loadStripe(env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);
    

验证

  • 重启Next.js开发服务器(确保环境变量重新加载)
  • 查看客户端打包后的代码,确认serverEnvSchema.ts未被包含在bundle中

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 21:30:00