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文件(之前所有变量放一个文件,问题依旧)
- 两种方式调用客户端公开环境变量:
- 通过Zod解析的clientEnvSchema:
import { env } from "@/lib/zod_schemas/clientEnvSchema"; const stripePromise = loadStripe( env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, ); - 直接使用
process.env:// import { env } from "@/lib/zod_schemas/clientEnvSchema"; const stripePromise = loadStripe( process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!, );
- 通过Zod解析的clientEnvSchema:
两种方式均无法解决问题。
补充说明
该Zod错误仅在标记为"use client"的客户端组件中出现,服务端组件中可以正常打印变量值且无错误。
解决方案
核心原因
客户端组件在浏览器运行时,未加NEXT_PUBLIC_前缀的服务端专属变量(如API_BASE_URL、STRIPE_SECRET_KEY)不会被注入客户端。如果serverEnvSchema.ts被客户端组件间接引入(比如通过服务端动作checkout的依赖链),就会导致客户端执行parse()时这些变量为undefined,触发Zod校验错误。
修复步骤
限制服务端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, });排查依赖链,避免客户端引入服务端模块
检查checkout服务端动作的代码,确保它引入的serverEnvSchema不会被客户端组件间接加载。服务端动作本身由Next.js处理为服务端执行,但其依赖的模块必须保证是服务端专属的。客户端仅使用客户端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
相关产品推荐
相关产品推荐

