vercel-deployment
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVercel Deployment
Vercel 部署指南
When to Use This Skill
适用场景
- Configuring for deployments
vercel.json - Setting environment variables via CLI
- Deploying monorepos
- Troubleshooting build failures
- Understanding preview vs production deployments
- 为部署配置
vercel.json - 通过CLI设置环境变量
- 部署单仓库多项目(Monorepo)
- 排查构建失败问题
- 理解预览部署与生产部署的区别
vercel.json Configuration
vercel.json 配置
Minimal Configuration
最简配置
json
{
"$schema": "https://openapi.vercel.sh/vercel.json"
}Most projects don't need - Vercel auto-detects frameworks.
vercel.jsonjson
{
"$schema": "https://openapi.vercel.sh/vercel.json"
}大多数项目不需要 ——Vercel会自动检测框架。
vercel.jsonCommon Configuration
常见配置
json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "npm run build",
"outputDirectory": "dist",
"installCommand": "npm install",
"framework": "nextjs",
"regions": ["iad1"],
"functions": {
"api/**/*.ts": {
"memory": 1024,
"maxDuration": 30
}
}
}json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "npm run build",
"outputDirectory": "dist",
"installCommand": "npm install",
"framework": "nextjs",
"regions": ["iad1"],
"functions": {
"api/**/*.ts": {
"memory": 1024,
"maxDuration": 30
}
}
}Redirects and Rewrites
重定向与地址重写
json
{
"redirects": [
{ "source": "/old-page", "destination": "/new-page", "permanent": true }
],
"rewrites": [
{ "source": "/api/:path*", "destination": "https://api.example.com/:path*" }
],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" }
]
}
]
}json
{
"redirects": [
{ "source": "/old-page", "destination": "/new-page", "permanent": true }
],
"rewrites": [
{ "source": "/api/:path*", "destination": "https://api.example.com/:path*" }
],
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" }
]
}
]
}Environment Variables
环境变量
Critical Gotcha: Trailing Newlines
重要陷阱:尾随换行符
<EXTREMELY-IMPORTANT>
When piping values to `vercel env add`, use `printf`, NEVER `echo`.
adds a trailing newline that becomes part of the value, breaking API keys and secrets.
</EXTREMELY-IMPORTANT>
echobash
undefined<EXTREMELY-IMPORTANT>
使用 `vercel env add` 传入值时,请使用 `printf`,绝对不要用 `echo`。
会添加一个尾随换行符,该换行符会成为变量值的一部分,导致API密钥和机密信息失效。
</EXTREMELY-IMPORTANT>
echobash
undefined❌ WRONG - echo adds trailing newline
❌ 错误用法 - echo会添加尾随换行符
echo "sk-abc123" | vercel env add SECRET_KEY production
echo "sk-abc123" | vercel env add SECRET_KEY production
✅ CORRECT - printf has no trailing newline
✅ 正确用法 - printf不会添加尾随换行符
printf "sk-abc123" | vercel env add SECRET_KEY production
printf "sk-abc123" | vercel env add SECRET_KEY production
✅ ALSO CORRECT - interactive prompt
✅ 同样正确 - 交互式输入
vercel env add SECRET_KEY production
vercel env add SECRET_KEY production
Then paste value when prompted
然后在提示时粘贴值
**Symptoms of trailing newline bug:**
- API calls fail with "invalid key"
- Authentication errors despite correct credentials
- Value looks correct in dashboard but doesn't work
**Diagnosis:**
```bash
**尾随换行符bug的症状:**
- API调用因“无效密钥”失败
- 凭据正确但仍出现认证错误
- 控制台中变量值显示正确,但实际无法使用
**诊断方法:**
```bashCheck for trailing newline
检查是否存在尾随换行符
vercel env pull .env.local
cat -A .env.local | grep SECRET_KEY
vercel env pull .env.local
cat -A .env.local | grep SECRET_KEY
If you see SECRET_KEY=sk-abc123$ - no newline (good)
如果显示 SECRET_KEY=sk-abc123$ - 无换行符(正常)
If you see SECRET_KEY=sk-abc123\n$ - has newline (bad)
如果显示 SECRET_KEY=sk-abc123\n$ - 存在换行符(异常)
undefinedundefinedEnvironment Types
环境变量类型
| Type | When Used | Example |
|---|---|---|
| Production deployments only | API keys, database URLs |
| Preview deployments (PRs, branches) | Staging API keys |
| Local dev via | Local overrides |
bash
undefined| 类型 | 使用场景 | 示例 |
|---|---|---|
| 仅生产部署 | API密钥、数据库地址 |
| 预览部署(PR、分支) | 预发布环境API密钥 |
| 通过 | 本地覆盖配置 |
bash
undefinedAdd to production only
仅添加到生产环境
vercel env add API_KEY production
vercel env add API_KEY production
Add to all environments
添加到所有环境
vercel env add API_KEY production preview development
vercel env add API_KEY production preview development
Pull to local .env
拉取到本地.env文件
vercel env pull .env.local
undefinedvercel env pull .env.local
undefinedFramework-Specific Prefixes
框架特定前缀
| Framework | Public Prefix | Private (server-only) |
|---|---|---|
| Next.js | | No prefix |
| Vite | | No prefix |
| Create React App | | No prefix |
bash
undefined| 框架 | 公开变量前缀 | 私有(仅服务器端) |
|---|---|---|
| Next.js | | 无前缀 |
| Vite | | 无前缀 |
| Create React App | | 无前缀 |
bash
undefinedClient-accessible (bundled into JS)
客户端可访问(会打包到JS中)
vercel env add NEXT_PUBLIC_API_URL production
vercel env add NEXT_PUBLIC_API_URL production
Server-only (API routes, SSR)
仅服务器端可用(API路由、SSR)
vercel env add DATABASE_URL production
---vercel env add DATABASE_URL production
---Monorepo Deployment
单仓库多项目(Monorepo)部署
Root Configuration
根目录配置
json
// vercel.json at repo root
{
"buildCommand": "cd apps/web && npm run build",
"outputDirectory": "apps/web/dist",
"installCommand": "npm install",
"rootDirectory": "apps/web"
}json
// 仓库根目录下的vercel.json
{
"buildCommand": "cd apps/web && npm run build",
"outputDirectory": "apps/web/dist",
"installCommand": "npm install",
"rootDirectory": "apps/web"
}Multiple Apps from One Repo
一个仓库部署多个应用
Create separate Vercel projects, each with different :
rootDirectoryProject 1 (Web App):
json
{
"rootDirectory": "apps/web"
}Project 2 (API):
json
{
"rootDirectory": "apps/api"
}创建独立的Vercel项目,每个项目使用不同的 :
rootDirectory项目1(Web应用):
json
{
"rootDirectory": "apps/web"
}项目2(API服务):
json
{
"rootDirectory": "apps/api"
}Turborepo / pnpm Workspaces
Turborepo / pnpm 工作区
json
{
"buildCommand": "cd ../.. && pnpm turbo build --filter=web",
"outputDirectory": ".next",
"rootDirectory": "apps/web"
}Common issue: Build fails because dependencies aren't installed.
Fix: Set install command at root level:
json
{
"installCommand": "cd ../.. && pnpm install",
"buildCommand": "cd ../.. && pnpm turbo build --filter=web",
"rootDirectory": "apps/web"
}json
{
"buildCommand": "cd ../.. && pnpm turbo build --filter=web",
"outputDirectory": ".next",
"rootDirectory": "apps/web"
}常见问题: 构建失败,因为依赖未安装。
解决方法: 在根级别设置安装命令:
json
{
"installCommand": "cd ../.. && pnpm install",
"buildCommand": "cd ../.. && pnpm turbo build --filter=web",
"rootDirectory": "apps/web"
}Next.js Specific
Next.js 专属内容
Common Build Errors
常见构建错误
Error:
useX must be used within ProviderError: useAuth must be used within an AuthProvider
Error occurred prerendering page "/dashboard"Cause: Next.js tries to statically prerender pages that use React Context.
Fix: Wrap providers in :
layout.tsxtsx
// src/components/Providers.tsx
'use client'
export default function Providers({ children }) {
return <AuthProvider>{children}</AuthProvider>
}
// src/app/layout.tsx
import Providers from '../components/Providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}Error:
NEXT_PUBLIC_* undefined at build timeCause: Environment variable not set in Vercel project settings.
Fix:
- Add variable in Vercel dashboard → Settings → Environment Variables
- Or use
vercel env add NEXT_PUBLIC_API_URL production - Redeploy (env vars are baked in at build time for )
NEXT_PUBLIC_*
Error: /
Dynamic server usageopted out of static renderingError: Dynamic server usage: cookies
Route /dashboard couldn't be rendered staticallyCause: Using dynamic functions (, ) in pages Vercel tries to statically generate.
cookies()headers()Fix: Export dynamic route config:
tsx
// Force dynamic rendering
export const dynamic = 'force-dynamic'
// Or force static
export const dynamic = 'force-static'错误:
useX must be used within ProviderError: useAuth must be used within an AuthProvider
Error occurred prerendering page "/dashboard"原因: Next.js尝试对使用React Context的页面进行静态预渲染。
解决方法: 在中包裹Provider:
layout.tsxtsx
// src/components/Providers.tsx
'use client'
export default function Providers({ children }) {
return <AuthProvider>{children}</AuthProvider>
}
// src/app/layout.tsx
import Providers from '../components/Providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}错误:
NEXT_PUBLIC_* undefined at build time原因: Vercel项目设置中未配置该环境变量。
解决方法:
- 在Vercel控制台 → 设置 → 环境变量中添加变量
- 或者使用命令
vercel env add NEXT_PUBLIC_API_URL production - 重新部署(类型变量会在构建时嵌入代码)
NEXT_PUBLIC_*
错误: /
Dynamic server usageopted out of static renderingError: Dynamic server usage: cookies
Route /dashboard couldn't be rendered statically原因: 在Vercel尝试静态生成的页面中使用了动态函数(、)。
cookies()headers()解决方法: 导出动态路由配置:
tsx
// 强制动态渲染
export const dynamic = 'force-dynamic'
// 或者强制静态渲染
export const dynamic = 'force-static'Output Modes
输出模式
js
// next.config.js
module.exports = {
output: 'standalone', // For Docker/self-hosting
// output: 'export', // Static HTML export (no server features)
}Vercel auto-detects - usually don't need to set this.
js
// next.config.js
module.exports = {
output: 'standalone', // 用于Docker/自托管
// output: 'export', // 静态HTML导出(无服务器功能)
}Vercel会自动检测——通常无需手动设置。
Preview Deployments
预览部署
Automatic Previews
自动预览
Every push to a non-production branch creates a preview deployment:
- →
feature-branchproject-feature-branch-xxx.vercel.app - PR comments show preview URL automatically
每次推送到非生产分支都会创建一个预览部署:
- →
feature-branchproject-feature-branch-xxx.vercel.app - PR评论会自动显示预览地址
Preview Environment Variables
预览环境变量
Preview deployments use environment variables:
previewbash
undefined预览部署使用类型的环境变量:
previewbash
undefinedProduction database
生产数据库
vercel env add DATABASE_URL production
vercel env add DATABASE_URL production
Value: postgres://prod-db...
值: postgres://prod-db...
Preview/staging database
预览/预发布数据库
vercel env add DATABASE_URL preview
vercel env add DATABASE_URL preview
Value: postgres://staging-db...
值: postgres://staging-db...
undefinedundefinedDisable Previews
禁用预览
json
// vercel.json
{
"git": {
"deploymentEnabled": {
"main": true,
"feature/*": false
}
}
}json
// vercel.json
{
"git": {
"deploymentEnabled": {
"main": true,
"feature/*": false
}
}
}CLI Commands
CLI 命令
Installation & Auth
安装与认证
bash
npm install -g vercel
vercel login
vercel whoamibash
npm install -g vercel
vercel login
vercel whoamiDeployment
部署
bash
undefinedbash
undefinedDeploy to preview
部署到预览环境
vercel
vercel
Deploy to production
部署到生产环境
vercel --prod
vercel --prod
Deploy without prompts
无提示部署
vercel --prod --yes
vercel --prod --yes
Deploy specific directory
部署指定目录
vercel ./dist --prod
undefinedvercel ./dist --prod
undefinedProject Management
项目管理
bash
undefinedbash
undefinedLink local project to Vercel
将本地项目关联到Vercel
vercel link
vercel link
List deployments
列出所有部署
vercel list
vercel list
View deployment logs
查看部署日志
vercel logs <deployment-url>
vercel logs <deployment-url>
Inspect deployment
查看部署详情
vercel inspect <deployment-url>
vercel inspect <deployment-url>
Promote deployment to production
将部署升级为生产环境
vercel promote <deployment-url>
undefinedvercel promote <deployment-url>
undefinedEnvironment Variables
环境变量
bash
undefinedbash
undefinedList env vars
列出所有环境变量
vercel env ls
vercel env ls
Add env var
添加环境变量
vercel env add VAR_NAME production
vercel env add VAR_NAME production
Remove env var
删除环境变量
vercel env rm VAR_NAME production
vercel env rm VAR_NAME production
Pull env vars to local file
将环境变量拉取到本地文件
vercel env pull .env.local
undefinedvercel env pull .env.local
undefinedRollback
回滚
bash
undefinedbash
undefinedList recent deployments
列出最近的部署
vercel list
vercel list
Promote previous deployment to production
将之前的部署升级为生产环境
vercel promote <previous-deployment-url>
vercel promote <previous-deployment-url>
Or instant rollback in dashboard
或者在控制台一键回滚
Deployments → ... → Promote to Production
部署记录 → ... → 升级为生产环境
---
---Serverless Functions
无服务器函数
API Routes (Next.js)
API路由(Next.js)
typescript
// app/api/hello/route.ts (App Router)
export async function GET(request: Request) {
return Response.json({ message: 'Hello' })
}
// pages/api/hello.ts (Pages Router)
export default function handler(req, res) {
res.status(200).json({ message: 'Hello' })
}typescript
// app/api/hello/route.ts(App Router)
export async function GET(request: Request) {
return Response.json({ message: 'Hello' })
}
// pages/api/hello.ts(Pages Router)
export default function handler(req, res) {
res.status(200).json({ message: 'Hello' })
}Standalone Functions
独立函数
typescript
// api/hello.ts
import type { VercelRequest, VercelResponse } from '@vercel/node'
export default function handler(req: VercelRequest, res: VercelResponse) {
res.status(200).json({ message: 'Hello' })
}typescript
// api/hello.ts
import type { VercelRequest, VercelResponse } from '@vercel/node'
export default function handler(req: VercelRequest, res: VercelResponse) {
res.status(200).json({ message: 'Hello' })
}Function Configuration
函数配置
json
// vercel.json
{
"functions": {
"api/**/*.ts": {
"memory": 1024, // MB (128-3008)
"maxDuration": 30 // seconds (max 300 on Pro)
}
}
}json
// vercel.json
{
"functions": {
"api/**/*.ts": {
"memory": 1024, // 内存大小(MB,范围128-3008)
"maxDuration": 30 // 最大运行时长(秒,专业版最大300)
}
}
}Edge Functions
Edge函数
typescript
// app/api/edge/route.ts
export const runtime = 'edge'
export async function GET(request: Request) {
return new Response('Hello from the edge!')
}Edge vs Serverless:
| Feature | Edge | Serverless |
|---|---|---|
| Cold start | ~0ms | 250-500ms |
| Memory | 128MB | Up to 3GB |
| Duration | 30s | Up to 300s |
| Node.js APIs | Limited | Full |
| Location | All regions | Selected region |
typescript
// app/api/edge/route.ts
export const runtime = 'edge'
export async function GET(request: Request) {
return new Response('Hello from the edge!')
}Edge函数 vs 无服务器函数:
| 特性 | Edge | 无服务器 |
|---|---|---|
| 冷启动 | ~0ms | 250-500ms |
| 内存 | 128MB | 最高3GB |
| 运行时长 | 30s | 最高300s |
| Node.js API | 受限 | 完整支持 |
| 部署位置 | 所有区域 | 选定区域 |
Async Operations & Background Tasks
异步操作与后台任务
Critical: Vercel Kills Background Tasks
重要提示:Vercel会终止后台任务
<EXTREMELY-IMPORTANT>
Vercel terminates serverless functions as soon as the response is sent. Any background async tasks (IIFE, `.then()`, `.catch()`) may not complete.
</EXTREMELY-IMPORTANT>
typescript
// ❌ BROKEN - Vercel may kill this before completion
;(async () => {
const email = await lookupEmail(phone) // Takes 500ms
await sendEmail(email, content) // Never runs!
})().catch(err => console.error(err))
return NextResponse.json({ success: true }) // Response sent, function diestypescript
// ✅ WORKING - Everything completes before response
const email = await lookupEmail(phone)
try {
await sendEmail(email, content)
} catch (err) {
console.error('Email failed:', err)
}
return NextResponse.json({ success: true })Trade-off: Response is slower (adds ~1-2s) but async operations are guaranteed to complete.
<EXTREMELY-IMPORTANT>
一旦响应发送完成,Vercel会立即终止无服务器函数。任何后台异步任务(立即执行函数、`.then()`、`.catch()`)可能无法完成。
</EXTREMELY-IMPORTANT>
typescript
// ❌ 错误用法 - Vercel可能在任务完成前终止函数
;(async () => {
const email = await lookupEmail(phone) // 耗时500ms
await sendEmail(email, content) // 永远不会执行!
})().catch(err => console.error(err))
return NextResponse.json({ success: true }) // 响应已发送,函数终止typescript
// ✅ 正确用法 - 所有操作在响应前完成
const email = await lookupEmail(phone)
try {
await sendEmail(email, content)
} catch (err) {
console.error('Email failed:', err)
}
return NextResponse.json({ success: true })权衡: 响应速度变慢(增加约1-2秒),但异步操作能保证完成。
External API Timeouts
外部API超时
Fetch requests to external APIs can hang indefinitely on Vercel, causing the function to timeout without completing.
typescript
// Add AbortController with explicit timeout
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 8000) // 8s timeout
const response = await fetch(url, {
headers: { 'Referer': 'https://example.com/' },
signal: controller.signal,
})
clearTimeout(timeoutId)Vercel上的外部API请求可能无限挂起,导致函数超时且无法完成。
typescript
// 添加AbortController设置显式超时
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 8000) // 8秒超时
const response = await fetch(url, {
headers: { 'Referer': 'https://example.com/' },
signal: controller.signal,
})
clearTimeout(timeoutId)Recommended Pattern for Notifications
推荐的通知模式
typescript
export async function POST(req: NextRequest) {
// ... validation and business logic ...
// Do email lookup in main request flow (not background)
const email = phoneNumber ? await lookupEmailByPhone(phoneNumber) : null
// Send notification synchronously (await ensures completion)
if (phoneNumber) {
try {
if (email) {
await sendEmail({ to: email, ...params })
} else {
await sendSMS({ to: phoneNumber, ...params })
}
} catch (err) {
console.error('[Notification] Failed:', err)
// Don't fail the request if notification fails
}
}
return NextResponse.json({ success: true })
}typescript
export async function POST(req: NextRequest) {
// ... 验证和业务逻辑 ...
// 在主请求流程中执行邮箱查询(而非后台)
const email = phoneNumber ? await lookupEmailByPhone(phoneNumber) : null
// 同步发送通知(await确保完成)
if (phoneNumber) {
try {
if (email) {
await sendEmail({ to: email, ...params })
} else {
await sendSMS({ to: phoneNumber, ...params })
}
} catch (err) {
console.error('[Notification] Failed:', err)
// 通知失败时不要让请求失败
}
}
return NextResponse.json({ success: true })
}Debugging Serverless Functions
调试无服务器函数
-
Create debug endpoints to isolate components:
- - Test email lookup
/api/debug-email-lookup?phone=X - - Test email sending
/api/debug-email-send?to=X
-
Add logging at each step:typescript
console.log(`[Notification] Starting for ${ref}, phone=${phone}`) const email = await lookupEmail(phone) console.log(`[Notification] Lookup returned: ${email || 'null'}`) console.log(`[Notification] SENDING EMAIL to ${email}`) const result = await sendEmail(...) console.log(`[Notification] Result:`, result) -
Test locally first - Local dev shows full logs, Vercel logs are delayed/incomplete
-
Check Vercel logs (but they're unreliable):bash
vercel logs https://your-app.vercel.app | grep -E "(Notification|Email|SMS)"
-
创建调试端点以隔离组件:
- - 测试邮箱查询
/api/debug-email-lookup?phone=X - - 测试邮件发送
/api/debug-email-send?to=X
-
在每个步骤添加日志:typescript
console.log(`[Notification] Starting for ${ref}, phone=${phone}`) const email = await lookupEmail(phone) console.log(`[Notification] Lookup returned: ${email || 'null'}`) console.log(`[Notification] SENDING EMAIL to ${email}`) const result = await sendEmail(...) console.log(`[Notification] Result:`, result) -
先在本地测试 - 本地开发显示完整日志,Vercel日志存在延迟/不完整问题
-
查看Vercel日志(但可靠性不高):bash
vercel logs https://your-app.vercel.app | grep -E "(Notification|Email|SMS)"
Key Takeaways
关键要点
- Never trust background tasks on Vercel - Always await
- Trim API keys - Env vars can have hidden newlines (see Environment Variables section)
- Add timeouts to external API calls - Prevent indefinite hangs
- Test locally first - Full visibility into logs
- Debug endpoints are invaluable - Isolate components for testing
- 永远不要信任Vercel上的后台任务 - 始终使用await
- 修剪API密钥 - 环境变量可能包含隐藏的换行符(见环境变量章节)
- 为外部API调用添加超时 - 防止无限挂起
- 先在本地测试 - 日志完全可见
- 调试端点非常有用 - 隔离组件进行测试
Vercel AI Gateway
Vercel AI 网关
Overview
概述
Vercel AI Gateway provides a unified interface for calling AI models (OpenAI, Anthropic, etc.) with automatic OIDC authentication on Vercel deployments.
Vercel AI网关提供统一接口,用于调用AI模型(OpenAI、Anthropic等),并在Vercel部署中自动实现OIDC认证。
Installation
安装
bash
npm install aibash
npm install aiCorrect Usage Pattern
正确使用模式
<EXTREMELY-IMPORTANT>
Use `import { generateText } from 'ai'` with a model string. Do NOT use `@ai-sdk/anthropic` or custom providers for Vercel AI Gateway.
</EXTREMELY-IMPORTANT>
typescript
// ✅ CORRECT - Simple pattern from docs
import { generateText } from 'ai'
const { text } = await generateText({
model: 'anthropic/claude-sonnet-4', // provider/model format
system: 'You are a helpful assistant.',
prompt: 'What is 2+2?',
})typescript
// ❌ WRONG - Over-complicated patterns that break
import { anthropic } from '@ai-sdk/anthropic'
import { gateway } from '@vercel/ai-sdk-gateway'
// These cause FUNCTION_INVOCATION_FAILED errors<EXTREMELY-IMPORTANT>
使用 `import { generateText } from 'ai'` 搭配模型字符串。不要为Vercel AI网关使用 `@ai-sdk/anthropic` 或自定义提供者。
</EXTREMELY-IMPORTANT>
typescript
// ✅ 正确用法 - 文档中的简单模式
import { generateText } from 'ai'
const { text } = await generateText({
model: 'anthropic/claude-sonnet-4', // 提供者/模型格式
system: 'You are a helpful assistant.',
prompt: 'What is 2+2?',
})typescript
// ❌ 错误用法 - 过于复杂的模式会导致失败
import { anthropic } from '@ai-sdk/anthropic'
import { gateway } from '@vercel/ai-sdk-gateway'
// 这些会导致FUNCTION_INVOCATION_FAILED错误Model String Format
模型字符串格式
Use format:
provider/model-name| Provider | Model String |
|---|---|
| Anthropic | |
| Anthropic | |
| OpenAI | |
| OpenAI | |
使用格式:
provider/model-name| 提供者 | 模型字符串 |
|---|---|
| Anthropic | |
| Anthropic | |
| OpenAI | |
| OpenAI | |
Authentication
认证
On Vercel (Production/Preview)
在Vercel上(生产/预览环境)
OIDC authentication is automatic - no configuration needed. The AI SDK detects it's running on Vercel and uses OIDC tokens.
OIDC认证是自动的——无需配置。AI SDK会检测到运行在Vercel上,并使用OIDC令牌。
Local Development
本地开发
Use to proxy through Vercel and get the same OIDC auth:
vercel devbash
vercel devNote: Regular won't work for AI features - you must use locally.
npm run devvercel dev使用通过Vercel代理,获得相同的OIDC认证:
vercel devbash
vercel dev注意: 常规的无法用于AI功能——本地必须使用。
npm run devvercel devError Handling with Fallback
带降级的错误处理
typescript
import { generateText } from 'ai'
async function askAI(question: string) {
try {
const { text } = await generateText({
model: 'anthropic/claude-sonnet-4',
prompt: question,
})
return text
} catch (error) {
console.error('[AI Gateway] Error:', error)
// Fallback to non-AI behavior
return generateFallbackResponse(question)
}
}typescript
import { generateText } from 'ai'
async function askAI(question: string) {
try {
const { text } = await generateText({
model: 'anthropic/claude-sonnet-4',
prompt: question,
})
return text
} catch (error) {
console.error('[AI Gateway] Error:', error)
// 降级为非AI行为
return generateFallbackResponse(question)
}
}Common Errors
常见错误
Error:
FUNCTION_INVOCATION_FAILED- Cause: Using wrong import patterns or deprecated packages
- Fix: Use simple pattern
import { generateText } from 'ai'
Error:
AI analysis is temporarily unavailable- Cause: AI Gateway call failing, falling back to error handler
- Debug: Check Vercel logs for the actual error
- Common fixes:
- Ensure is set for local dev
AI_GATEWAY_API_KEY - Use correct model string format
- Check network connectivity to AI provider
- Ensure
Error:
Timeout waiting for AI response- Cause: Model taking too long to respond
- Fix: Add maxDuration to function config:
json
{ "functions": { "api/**/*.ts": { "maxDuration": 60 } } }
错误:
FUNCTION_INVOCATION_FAILED- 原因: 使用了错误的导入模式或已弃用的包
- 解决方法: 使用简单的模式
import { generateText } from 'ai'
错误:
AI analysis is temporarily unavailable- 原因: AI网关调用失败,触发错误处理
- 调试: 查看Vercel日志获取实际错误
- 常见解决方法:
- 确保本地开发时已设置
AI_GATEWAY_API_KEY - 使用正确的模型字符串格式
- 检查与AI提供者的网络连接
- 确保本地开发时已设置
错误:
Timeout waiting for AI response- 原因: 模型响应时间过长
- 解决方法: 为函数配置添加maxDuration:
json
{ "functions": { "api/**/*.ts": { "maxDuration": 60 } } }
Streaming Responses
流式响应
typescript
import { streamText } from 'ai'
export async function POST(req: Request) {
const { prompt } = await req.json()
const result = await streamText({
model: 'anthropic/claude-sonnet-4',
prompt,
})
return result.toDataStreamResponse()
}typescript
import { streamText } from 'ai'
export async function POST(req: Request) {
const { prompt } = await req.json()
const result = await streamText({
model: 'anthropic/claude-sonnet-4',
prompt,
})
return result.toDataStreamResponse()
}Best Practices
最佳实践
- Always handle errors - AI calls can fail; have fallback behavior
- Don't over-engineer - The simple pattern works; avoid custom providers
- Test locally first - Use or
AI_GATEWAY_API_KEYvercel dev - Log errors - Include prefix for easy filtering
[AI Gateway] - Mock in tests - Avoid real API calls in unit tests:
typescript
vi.mock('ai', () => ({ generateText: vi.fn().mockRejectedValue(new Error('Mocked')), }))
- 始终处理错误 - AI调用可能失败;要有降级行为
- 不要过度设计 - 简单模式即可工作;避免使用自定义提供者
- 先在本地测试 - 使用或
AI_GATEWAY_API_KEYvercel dev - 记录错误 - 包含前缀以便过滤
[AI Gateway] - 在测试中模拟 - 单元测试中避免真实API调用:
typescript
vi.mock('ai', () => ({ generateText: vi.fn().mockRejectedValue(new Error('Mocked')), }))
Build Caching
构建缓存
Turborepo Remote Cache
Turborepo 远程缓存
bash
undefinedbash
undefinedLink to Vercel remote cache
关联到Vercel远程缓存
npx turbo login
npx turbo link
```json
// turbo.json
{
"remoteCache": {
"signature": true
}
}npx turbo login
npx turbo link
```json
// turbo.json
{
"remoteCache": {
"signature": true
}
}Clear Build Cache
清除构建缓存
If builds are stale or broken:
- Dashboard: Settings → General → Build Cache → Purge
- CLI: Redeploy with
vercel --force
如果构建结果过期或失败:
- 控制台: 设置 → 常规 → 构建缓存 → 清除
- CLI: 使用重新部署
vercel --force
E2E Testing with Turso/Cloud Databases
使用Turso/云数据库的端到端测试
When running Playwright E2E tests against a Next.js app that supports both local SQLite and Turso, force local database mode to avoid socket timeouts and flaky tests.
typescript
// playwright.config.ts
webServer: {
// Unset Turso env vars to force local SQLite mode
command: 'TURSO_DATABASE_URL= TURSO_AUTH_TOKEN= npm run dev -- --port 3001',
url: 'http://localhost:3001',
timeout: 120000,
},Why: Long-running E2E tests (e.g., processing 280K+ records) can timeout waiting for Turso cloud responses. Local SQLite is faster and more reliable for testing.
See also: skill for complete E2E testing patterns.
nextjs-e2e-testing.md当针对同时支持本地SQLite和Turso的Next.js应用运行Playwright端到端测试时,强制使用本地数据库模式以避免套接字超时和不稳定的测试结果。
typescript
// playwright.config.ts
webServer: {
// 取消设置Turso环境变量以强制使用本地SQLite模式
command: 'TURSO_DATABASE_URL= TURSO_AUTH_TOKEN= npm run dev -- --port 3001',
url: 'http://localhost:3001',
timeout: 120000,
},原因: 长时间运行的端到端测试(例如处理28万+条记录)可能因等待Turso云响应而超时。本地SQLite速度更快,测试更可靠。
另见: 技能文档,获取完整的端到端测试模式。
nextjs-e2e-testing.mdCommon Issues
常见问题
Build Timeout
构建超时
Error:
Build exceeded maximum durationFixes:
- Upgrade plan (Hobby: 45min, Pro: 45min)
- Optimize build (parallel builds, caching)
- Use for monorepos
turbo prune
错误:
Build exceeded maximum duration解决方法:
- 升级套餐(免费版:45分钟,专业版:45分钟)
- 优化构建(并行构建、缓存)
- 对单仓库多项目使用
turbo prune
Memory Exceeded
内存不足
Error:
FATAL ERROR: JavaScript heap out of memoryFix: Increase Node memory in build command:
json
{
"buildCommand": "NODE_OPTIONS='--max-old-space-size=4096' npm run build"
}错误:
FATAL ERROR: JavaScript heap out of memory解决方法: 在构建命令中增加Node内存限制:
json
{
"buildCommand": "NODE_OPTIONS='--max-old-space-size=4096' npm run build"
}Module Not Found
模块未找到
Error:
Cannot find module 'x'Causes:
- Dependency in but needed at runtime
devDependencies - Case sensitivity (works on Mac, fails on Linux)
- Missing from
package.json
Fix: Move to or check case sensitivity.
dependencies错误:
Cannot find module 'x'原因:
- 依赖项在中,但运行时需要
devDependencies - 大小写问题(在Mac上正常,在Linux上失败)
- 中缺少该依赖
package.json
解决方法: 将依赖移到或检查大小写一致性。
dependenciesOAuth Integration
OAuth 集成
Callback URL Must Match Final Domain
回调URL必须匹配最终域名
If your domain redirects (e.g., → ), the OAuth callback URL must use the final destination domain:
example.comwww.example.comhttps://www.example.com/api/auth/callback ✓
https://example.com/api/auth/callback ✗ (if it redirects to www)如果你的域名会重定向(例如 → ),OAuth回调URL必须使用最终目标域名:
example.comwww.example.comhttps://www.example.com/api/auth/callback ✓
https://example.com/api/auth/callback ✗(如果重定向到www)Debugging OAuth Issues
调试OAuth问题
bash
undefinedbash
undefinedCheck redirect URL for corruption
检查重定向URL是否损坏
curl -s -I "https://your-app.com/api/auth/github" | grep location
Look for `%0A` (newline) or unexpected characters in `client_id` - indicates env var has trailing newline.
**Common errors:**
- `client_id and/or client_secret passed are incorrect` → Check for newlines in env vars
- `404 on callback` → Callback URL mismatch in OAuth app settings
---curl -s -I "https://your-app.com/api/auth/github" | grep location
查找`%0A`(换行符)或`client_id`中的意外字符——这表明环境变量包含尾随换行符。
**常见错误:**
- `client_id and/or client_secret passed are incorrect` → 检查环境变量是否有换行符
- `404 on callback` → OAuth应用设置中的回调URL不匹配
---GCP Workload Identity Federation (WIF)
GCP 工作负载身份联合(WIF)
Audience Mismatch
受众不匹配
Vercel OIDC tokens have but GCP providers often default to expecting .
aud: "https://vercel.com/{team-slug}"https://oidc.vercel.com/{team-slug}Diagnosis:
bash
gcloud iam workload-identity-pools providers describe {provider} \
--location=global \
--workload-identity-pool={pool} \
--project={project} \
--format="value(oidc.allowedAudiences)"Fix: Update allowed audience to match Vercel's token:
bash
gcloud iam workload-identity-pools providers update-oidc {provider} \
--location=global \
--workload-identity-pool={pool} \
--project={project} \
--allowed-audiences="https://vercel.com/{team-slug}"Vercel OIDC令牌的为,但GCP提供者通常默认期望。
aud"https://vercel.com/{team-slug}""https://oidc.vercel.com/{team-slug}"诊断:
bash
gcloud iam workload-identity-pools providers describe {provider} \
--location=global \
--workload-identity-pool={pool} \
--project={project} \
--format="value(oidc.allowedAudiences)"解决方法: 更新允许的受众以匹配Vercel的令牌:
bash
gcloud iam workload-identity-pools providers update-oidc {provider} \
--location=global \
--workload-identity-pool={pool} \
--project={project} \
--allowed-audiences="https://vercel.com/{team-slug}"OIDC Package
OIDC 包
Use , NOT the deprecated :
@vercel/functions/oidc@vercel/oidctypescript
// ❌ Old (deprecated, causes "getToken is not a function")
import { getToken } from '@vercel/oidc'
// ✅ New
import { getVercelOidcToken } from '@vercel/functions/oidc'使用,不要使用已弃用的:
@vercel/functions/oidc@vercel/oidctypescript
// ❌ 旧版(已弃用,会导致"getToken is not a function"错误)
import { getToken } from '@vercel/oidc'
// ✅ 新版
import { getVercelOidcToken } from '@vercel/functions/oidc'Newlines Break WIF
换行符会破坏WIF
If env vars have trailing newlines, the STS audience string becomes corrupted:
"//iam.googleapis.com/projects/123456\n/locations/global..."Symptoms:
- Debug endpoint shows in the
\nfieldstsAudience - STS exchange fails with "Invalid value for audience"
Fix: Re-add each WIF env var using (not dashboard copy-paste):
printfbash
printf "value" | vercel env add GCP_PROJECT_NUMBER production --force
printf "value" | vercel env add GCP_WORKLOAD_IDENTITY_POOL_ID production --force
printf "value" | vercel env add GCP_WORKLOAD_IDENTITY_PROVIDER_ID production --force
printf "value" | vercel env add GCP_SERVICE_ACCOUNT_EMAIL production --force如果环境变量包含尾随换行符,STS受众字符串会损坏:
"//iam.googleapis.com/projects/123456\n/locations/global..."症状:
- 调试端点显示字段中包含
stsAudience\n - STS交换因"Invalid value for audience"失败
解决方法: 使用重新添加每个WIF环境变量(不要从控制台复制粘贴):
printfbash
printf "value" | vercel env add GCP_PROJECT_NUMBER production --force
printf "value" | vercel env add GCP_WORKLOAD_IDENTITY_POOL_ID production --force
printf "value" | vercel env add GCP_WORKLOAD_IDENTITY_PROVIDER_ID production --force
printf "value" | vercel env add GCP_SERVICE_ACCOUNT_EMAIL production --forceDomains
域名
Add Custom Domain
添加自定义域名
bash
vercel domains add example.combash
vercel domains add example.comDNS Configuration
DNS 配置
| Type | Name | Value |
|---|---|---|
| A | @ | 76.76.21.21 |
| CNAME | www | cname.vercel-dns.com |
| 类型 | 名称 | 值 |
|---|---|---|
| A | @ | 76.76.21.21 |
| CNAME | www | cname.vercel-dns.com |
SSL
SSL
Automatic via Let's Encrypt. No configuration needed.
通过Let's Encrypt自动配置。无需手动操作。
Quick Reference
快速参考
bash
undefinedbash
undefinedDeploy to production
部署到生产环境
vercel --prod
vercel --prod
Add env var (use printf!)
添加环境变量(请使用printf!)
printf "value" | vercel env add KEY production
printf "value" | vercel env add KEY production
Pull env vars locally
拉取环境变量到本地
vercel env pull .env.local
vercel env pull .env.local
View logs
查看日志
vercel logs <url>
vercel logs <url>
Rollback
回滚
vercel promote <previous-url>
vercel promote <previous-url>
Clear cache and redeploy
清除缓存并重新部署
vercel --force --prod
---vercel --force --prod
---Fluid Compute & Function Duration
Fluid Compute 与函数运行时长
Overview
概述
Fluid Compute is Vercel's enhanced serverless model providing longer timeouts, optimized concurrency, and better cold start performance.
Fluid Compute是Vercel的增强型无服务器模型,提供更长的超时时间、优化的并发能力和更好的冷启动性能。
Duration Limits
时长限制
| Plan | Without Fluid Compute | With Fluid Compute |
|---|---|---|
| Hobby | 10s default, 60s max | 300s default, 300s max |
| Pro | 15s default, 300s max | 300s default, 800s max |
| Enterprise | 15s default, 300s max | 300s default, 800s max |
| 套餐 | 未启用Fluid Compute | 已启用Fluid Compute |
|---|---|---|
| 免费版 | 默认10秒,最大60秒 | 默认300秒,最大300秒 |
| 专业版 | 默认15秒,最大300秒 | 默认300秒,最大800秒 |
| 企业版 | 默认15秒,最大300秒 | 默认300秒,最大800秒 |
Enabling Fluid Compute
启用Fluid Compute
Two independent settings must BOTH be configured:
-
Dashboard Toggle (Project-wide)
- Go to Project Settings → Functions
- Find "Fluid Compute" section
- Toggle ON
- Click Save
- Redeploy (changes only apply to new deployments)
-
Dashboard Max Duration (Project-wide)
- Go to Project Settings → Functions
- Find "Function Max Duration" section
- Set to desired value (e.g., 300)
- Click Save
-
vercel.json (Per-deployment override)json
{ "$schema": "https://openapi.vercel.sh/vercel.json", "fluid": true }Note:in vercel.json only enables Fluid Compute for that specific deployment, NOT project-wide."fluid": true
必须同时配置两个独立设置:
-
控制台开关(项目级)
- 进入项目设置 → 函数
- 找到“Fluid Compute”部分
- 开启开关
- 点击保存
- 重新部署(更改仅对新部署生效)
-
控制台最大运行时长(项目级)
- 进入项目设置 → 函数
- 找到“函数最大运行时长”部分
- 设置为所需值(例如300)
- 点击保存
-
vercel.json(部署级覆盖)json
{ "$schema": "https://openapi.vercel.sh/vercel.json", "fluid": true }注意:vercel.json中的仅对该特定部署启用Fluid Compute,而非项目级全局启用。"fluid": true
Configuring maxDuration
配置maxDuration
Method 1: In route.ts (App Router - Recommended)
typescript
// src/app/api/my-function/route.ts
export const maxDuration = 300; // seconds
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export async function GET(request: Request) {
// ...
}Method 2: In vercel.json
json
{
"functions": {
"src/app/api/**/route.ts": {
"maxDuration": 300
}
}
}方法1:在route.ts中(App Router - 推荐)
typescript
// src/app/api/my-function/route.ts
export const maxDuration = 300; // 秒
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export async function GET(request: Request) {
// ...
}方法2:在vercel.json中
json
{
"functions": {
"src/app/api/**/route.ts": {
"maxDuration": 300
}
}
}Path Patterns for vercel.json
vercel.json中的路径模式
<EXTREMELY-IMPORTANT>
When using a `src/` directory, you MUST include `src/` in the path pattern.
</EXTREMELY-IMPORTANT>
| Project Structure | Correct Pattern | Wrong Pattern |
|---|---|---|
| | |
| | |
| Pages Router | | |
For App Router specifically:
- Use pattern, not
**/route.ts**/*.ts - The pattern must match the actual route handler files
<EXTREMELY-IMPORTANT>
如果项目使用`src/`目录,路径模式中必须包含`src/`。
</EXTREMELY-IMPORTANT>
| 项目结构 | 正确模式 | 错误模式 |
|---|---|---|
| | |
| | |
| Pages Router | | |
对于App Router:
- 使用模式,而非
**/route.ts**/*.ts - 模式必须与实际路由处理文件匹配
Known Issue: 60s Timeout Despite Pro Plan
已知问题:专业版仍出现60秒超时
Symptoms:
- Pro plan confirmed
- in vercel.json
"fluid": true - in route.ts
maxDuration = 300 - Function still times out at exactly 60 seconds
Root Cause: Fluid Compute is not being applied at the platform level despite settings.
Community Reports:
- Vercel timing out at 60s on Pro plan - Closed without resolution
- Max Duration not being used
- GitHub: maxDuration not honored
Troubleshooting Checklist:
- ✅ Verify Dashboard toggle is ON (not just vercel.json)
- ✅ Verify Dashboard "Function Max Duration" is set
- ✅ Verify path pattern matches your file structure
- ✅ Redeploy after any settings change
- ✅ Check if using Node.js runtime (Edge has different limits)
- ❌ If all above are correct → Contact Vercel Support
Workaround: Split Long-Running Tasks
If Fluid Compute won't activate, split work into multiple endpoints that each complete under 60s:
json
{
"crons": [
{
"path": "/api/cron/poll-nodes",
"schedule": "*/5 * * * *"
},
{
"path": "/api/cron/poll-validators",
"schedule": "*/5 * * * *"
}
]
}症状:
- 已确认使用专业版
- vercel.json中已设置
"fluid": true - route.ts中已设置
maxDuration = 300 - 函数仍在60秒时超时
根本原因: 尽管已配置,但平台层面未应用Fluid Compute。
社区反馈:
故障排除清单:
- ✅ 确认控制台开关已开启(不只是vercel.json中配置)
- ✅ 确认控制台“函数最大运行时长”已设置
- ✅ 确认路径模式与文件结构匹配
- ✅ 更改设置后重新部署
- ✅ 检查是否使用Node.js运行时(Edge函数有不同限制)
- ❌ 如果以上都正确 → 联系Vercel支持
临时解决方法:拆分长时任务
如果Fluid Compute无法激活,将工作拆分为多个端点,每个端点在60秒内完成:
json
{
"crons": [
{
"path": "/api/cron/poll-nodes",
"schedule": "*/5 * * * *"
},
{
"path": "/api/cron/poll-validators",
"schedule": "*/5 * * * *"
}
]
}Cron Jobs
定时任务
json
{
"crons": [
{
"path": "/api/cron/my-job",
"schedule": "*/5 * * * *"
}
]
}Cron Authentication:
- Vercel adds header
Authorization: Bearer <CRON_SECRET> - Set env var to protect endpoint
CRON_SECRET - Verify in your handler:
typescript
const authHeader = request.headers.get('authorization'); if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); }
json
{
"crons": [
{
"path": "/api/cron/my-job",
"schedule": "*/5 * * * *"
}
]
}定时任务认证:
- Vercel会添加请求头
Authorization: Bearer <CRON_SECRET> - 设置环境变量以保护端点
CRON_SECRET - 在处理程序中验证:
typescript
const authHeader = request.headers.get('authorization'); if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); }
Testing Functions Locally
本地测试函数
bash
undefinedbash
undefinedUse vercel dev for full Vercel environment simulation
使用vercel dev模拟完整Vercel环境
vercel dev
vercel dev
Or use vercel curl to test deployed endpoints with auth bypass
或使用vercel curl测试已部署的端点并绕过认证
vercel curl /api/cron/my-job -- --header "Authorization: Bearer $CRON_SECRET"
undefinedvercel curl /api/cron/my-job -- --header "Authorization: Bearer $CRON_SECRET"
undefinedRuntime Support
运行时支持
Fluid Compute currently supports:
- Node.js (version 20+)
- Python
- Edge (different limits apply)
- Bun
- Rust
Fluid Compute目前支持:
- Node.js(20+版本)
- Python
- Edge(限制不同)
- Bun
- Rust