你本地 .env 里有 NEXT_PUBLIC_SUPABASE_URL 和 NEXT_PUBLIC_SUPABASE_ANON_KEY,调用 createClient(url, anonKey) 正常工作。部署到 Vercel / Netlify / Cloudflare Pages 后,前端 console 报:
TypeError: Cannot read properties of undefined (reading 'auth')
Error: supabaseUrl is required.
或者更安静——所有 Supabase 调用 silently 失败,页面看似加载但没数据。这是部署平台没拿到 env、或前缀不对、或 build cache 旧导致的”process.env.X 在 client bundle 里是 undefined”。
理解关键:env vars 不会”自动从 .env 同步到云”。每个平台都要单独配置;前端 bundle 里能用的 env 必须有特定前缀(NEXT_PUBLIC_ / VITE_ / PUBLIC_)。
常见原因
按命中率从高到低:
1. 部署平台压根没设这两个 env
最高频。你本地有 .env,但 Vercel dashboard 里完全没添加。.env 不在 git(也不该在),所以部署后 process.env.X === undefined。
如何判断:Vercel / Netlify / CF Pages 的 dashboard → Environment Variables → 看是否存在。
2. 前缀和框架不匹配
Next.js → NEXT_PUBLIC_X (client 端可见)
Vite → VITE_X (import.meta.env.VITE_X)
Astro → PUBLIC_X (import.meta.env.PUBLIC_X)
SvelteKit → PUBLIC_X ($env/static/public)
Remix → 不强制前缀,但建议用 PUBLIC_
少了前缀的 env 不会被打进 client bundle,访问就是 undefined。
如何判断:你的 env 名称带正确前缀吗?
3. build cache 缓存了旧 env
你刚刚加了 env,但 Vercel 用 cached build artifact 部署 → bundle 里仍然没有新 env。
如何判断:刚改完 env 立刻 redeploy 但问题没解决。
4. 只设了 Preview,没设 Production
Vercel 的 env scope 分 Development / Preview / Production。只勾了 Preview,Production 部署照样 undefined。
如何判断:Vercel dashboard 里看每个 env var 的 environment 勾选范围。
5. 用了 server-side only env 在 client 端
SUPABASE_SERVICE_ROLE_KEY(不带 NEXT_PUBLIC_ 前缀)在 server 才能读,client 端访问就是 undefined——而且这是好事,service role key 绝不能给 client。
如何判断:你试图在 client component 读 server-only env。
6. env value 末尾有空格 / 换行
复制 anon key 时多带了空格或换行符,初始化时 URL parse 失败。
如何判断:手动 trim 看是否解决。
最短修复路径
Step 1:到 Supabase 后台复制正确值
Supabase Dashboard → Project → Settings → API
→ Project URL(https://xxx.supabase.co)
→ anon public key(eyJ...长串)
注意复制时不要带前后空格。
Step 2:到部署平台添加 env
Vercel:
Settings → Environment Variables
→ Add NEXT_PUBLIC_SUPABASE_URL = https://xxx.supabase.co
→ Add NEXT_PUBLIC_SUPABASE_ANON_KEY = eyJ...
→ Environment: 勾 Production + Preview + Development(都勾)
Netlify:
Site settings → Environment variables → Add a variable
Cloudflare Pages:
Settings → Environment variables → Production
确认所有环境(Production / Preview / Development)都勾上。
Step 3:grep 代码确认前缀
# Next.js
grep -r "process.env.SUPABASE" src/
# 应该都是 process.env.NEXT_PUBLIC_SUPABASE_*
# Vite
grep -r "import.meta.env" src/
# 应该都是 import.meta.env.VITE_SUPABASE_*
# Astro
grep -r "import.meta.env" src/
# 应该都是 import.meta.env.PUBLIC_SUPABASE_*
不匹配的改对:
// Next.js
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
Step 4:强制重新部署(清 cache)
Vercel:
Deployments → 最新 → ... → Redeploy
→ 取消勾选 "Use existing Build Cache"
Netlify:
Deploys → Trigger deploy → "Clear cache and deploy site"
Cloudflare Pages:
自动用最新 commit,但可以 force:retry deployment
Step 5:线上验证
打开生产 URL,DevTools Console:
// Next.js
console.log(process.env.NEXT_PUBLIC_SUPABASE_URL);
// 应输出 https://xxx.supabase.co,不是 undefined
// Vite
console.log(import.meta.env.VITE_SUPABASE_URL);
如果还 undefined → 前缀不对或 build cache 没清。
Step 6:boot-time 校验
加 fail-fast 校验,下次缺失立刻 build 失败而非 runtime 错:
// lib/env.ts
import { z } from 'zod';
const envSchema = z.object({
NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(20),
});
export const env = envSchema.parse({
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
});
build 时缺 env 直接挂,比 runtime 才发现强 10 倍。
Step 7:server-only env 分清
// ✅ client 可见
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
// ❌ 只 server 用,绝对不能加 NEXT_PUBLIC_ 前缀
SUPABASE_SERVICE_ROLE_KEY // service role 有完全 DB 权限,泄露 = 灾难
预防建议
- README / CLAUDE.md 列出所有必须 env,包括前缀
- 项目根放一个
.env.example(不含真值)跟.env同步,新人 clone 后照着配 - 用 zod 在 boot 时校验 env,缺失立刻 fail-fast
- 任何含
_KEY、_SECRET、_TOKEN的 env 永远不加 NEXT_PUBLIC_ / VITE_ / PUBLIC_ 前缀 - 加新 env 时立刻同步本地、Preview、Production 三处
- 改完 env 习惯性 “Clear cache and redeploy”,别假设增量 build 会带新值
- env 改动加到 PR template / deploy checklist,避免漏配
- 监控 boot logs,发现 env 校验失败立刻 alert