vercel-deployment

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Vercel Deployment

Vercel 部署指南

When to Use This Skill

适用场景

  • Configuring
    vercel.json
    for deployments
  • 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.json
- Vercel auto-detects frameworks.
json
{
  "$schema": "https://openapi.vercel.sh/vercel.json"
}
大多数项目不需要
vercel.json
——Vercel会自动检测框架。

Common 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`.
echo
adds a trailing newline that becomes part of the value, breaking API keys and secrets. </EXTREMELY-IMPORTANT>
bash
undefined
<EXTREMELY-IMPORTANT> 使用 `vercel env add` 传入值时,请使用 `printf`,绝对不要用 `echo`。
echo
会添加一个尾随换行符,该换行符会成为变量值的一部分,导致API密钥和机密信息失效。 </EXTREMELY-IMPORTANT>
bash
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调用因“无效密钥”失败
- 凭据正确但仍出现认证错误
- 控制台中变量值显示正确,但实际无法使用

**诊断方法:**
```bash

Check 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$ - 存在换行符(异常)

undefined
undefined

Environment Types

环境变量类型

TypeWhen UsedExample
production
Production deployments onlyAPI keys, database URLs
preview
Preview deployments (PRs, branches)Staging API keys
development
Local dev via
vercel dev
Local overrides
bash
undefined
类型使用场景示例
production
仅生产部署API密钥、数据库地址
preview
预览部署(PR、分支)预发布环境API密钥
development
通过
vercel dev
本地开发
本地覆盖配置
bash
undefined

Add 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
undefined
vercel env pull .env.local
undefined

Framework-Specific Prefixes

框架特定前缀

FrameworkPublic PrefixPrivate (server-only)
Next.js
NEXT_PUBLIC_*
No prefix
Vite
VITE_*
No prefix
Create React App
REACT_APP_*
No prefix
bash
undefined
框架公开变量前缀私有(仅服务器端)
Next.js
NEXT_PUBLIC_*
无前缀
Vite
VITE_*
无前缀
Create React App
REACT_APP_*
无前缀
bash
undefined

Client-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
rootDirectory
:
Project 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 Provider
Error: 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.tsx
:
tsx
// 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 time
Cause: Environment variable not set in Vercel project settings.
Fix:
  1. Add variable in Vercel dashboard → Settings → Environment Variables
  2. Or use
    vercel env add NEXT_PUBLIC_API_URL production
  3. Redeploy (env vars are baked in at build time for
    NEXT_PUBLIC_*
    )

Error:
Dynamic server usage
/
opted out of static rendering
Error: Dynamic server usage: cookies
Route /dashboard couldn't be rendered statically
Cause: Using dynamic functions (
cookies()
,
headers()
) in pages Vercel tries to statically generate.
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 Provider
Error: useAuth must be used within an AuthProvider
Error occurred prerendering page "/dashboard"
原因: Next.js尝试对使用React Context的页面进行静态预渲染。
解决方法:
layout.tsx
中包裹Provider:
tsx
// 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项目设置中未配置该环境变量。
解决方法:
  1. 在Vercel控制台 → 设置 → 环境变量中添加变量
  2. 或者使用命令
    vercel env add NEXT_PUBLIC_API_URL production
  3. 重新部署(
    NEXT_PUBLIC_*
    类型变量会在构建时嵌入代码)

错误:
Dynamic server usage
/
opted out of static rendering
Error: 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-branch
    project-feature-branch-xxx.vercel.app
  • PR comments show preview URL automatically
每次推送到非生产分支都会创建一个预览部署:
  • feature-branch
    project-feature-branch-xxx.vercel.app
  • PR评论会自动显示预览地址

Preview Environment Variables

预览环境变量

Preview deployments use
preview
environment variables:
bash
undefined
预览部署使用
preview
类型的环境变量:
bash
undefined

Production 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...

undefined
undefined

Disable 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 whoami
bash
npm install -g vercel
vercel login
vercel whoami

Deployment

部署

bash
undefined
bash
undefined

Deploy 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
undefined
vercel ./dist --prod
undefined

Project Management

项目管理

bash
undefined
bash
undefined

Link 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>
undefined
vercel promote <deployment-url>
undefined

Environment Variables

环境变量

bash
undefined
bash
undefined

List 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
undefined
vercel env pull .env.local
undefined

Rollback

回滚

bash
undefined
bash
undefined

List 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:
FeatureEdgeServerless
Cold start~0ms250-500ms
Memory128MBUp to 3GB
Duration30sUp to 300s
Node.js APIsLimitedFull
LocationAll regionsSelected 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无服务器
冷启动~0ms250-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 dies
typescript
// ✅ 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

调试无服务器函数

  1. Create debug endpoints to isolate components:
    • /api/debug-email-lookup?phone=X
      - Test email lookup
    • /api/debug-email-send?to=X
      - Test email sending
  2. 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)
  3. Test locally first - Local dev shows full logs, Vercel logs are delayed/incomplete
  4. Check Vercel logs (but they're unreliable):
    bash
    vercel logs https://your-app.vercel.app | grep -E "(Notification|Email|SMS)"
  1. 创建调试端点以隔离组件:
    • /api/debug-email-lookup?phone=X
      - 测试邮箱查询
    • /api/debug-email-send?to=X
      - 测试邮件发送
  2. 在每个步骤添加日志:
    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)
  3. 先在本地测试 - 本地开发显示完整日志,Vercel日志存在延迟/不完整问题
  4. 查看Vercel日志(但可靠性不高):
    bash
    vercel logs https://your-app.vercel.app | grep -E "(Notification|Email|SMS)"

Key Takeaways

关键要点

  1. Never trust background tasks on Vercel - Always await
  2. Trim API keys - Env vars can have hidden newlines (see Environment Variables section)
  3. Add timeouts to external API calls - Prevent indefinite hangs
  4. Test locally first - Full visibility into logs
  5. Debug endpoints are invaluable - Isolate components for testing

  1. 永远不要信任Vercel上的后台任务 - 始终使用await
  2. 修剪API密钥 - 环境变量可能包含隐藏的换行符(见环境变量章节)
  3. 为外部API调用添加超时 - 防止无限挂起
  4. 先在本地测试 - 日志完全可见
  5. 调试端点非常有用 - 隔离组件进行测试

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 ai
bash
npm install ai

Correct 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
provider/model-name
format:
ProviderModel String
Anthropic
anthropic/claude-sonnet-4
Anthropic
anthropic/claude-opus-4
OpenAI
openai/gpt-4o
OpenAI
openai/gpt-4-turbo
使用
provider/model-name
格式:
提供者模型字符串
Anthropic
anthropic/claude-sonnet-4
Anthropic
anthropic/claude-opus-4
OpenAI
openai/gpt-4o
OpenAI
openai/gpt-4-turbo

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
vercel dev
to proxy through Vercel and get the same OIDC auth:
bash
vercel dev
<EXTREMELY-IMPORTANT> You do NOT need individual provider API keys (like `ANTHROPIC_API_KEY`) when using Vercel AI Gateway. The gateway handles authentication via OIDC. </EXTREMELY-IMPORTANT>
Note: Regular
npm run dev
won't work for AI features - you must use
vercel dev
locally.
使用
vercel dev
通过Vercel代理,获得相同的OIDC认证:
bash
vercel dev
<EXTREMELY-IMPORTANT> 使用Vercel AI网关时,你不需要单独的提供者API密钥(如`ANTHROPIC_API_KEY`)。网关通过OIDC处理认证。 </EXTREMELY-IMPORTANT>
注意: 常规的
npm run dev
无法用于AI功能——本地必须使用
vercel dev

Error 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
    import { generateText } from 'ai'
    pattern
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
      AI_GATEWAY_API_KEY
      is set for local dev
    • Use correct model string format
    • Check network connectivity to AI provider
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

最佳实践

  1. Always handle errors - AI calls can fail; have fallback behavior
  2. Don't over-engineer - The simple pattern works; avoid custom providers
  3. Test locally first - Use
    AI_GATEWAY_API_KEY
    or
    vercel dev
  4. Log errors - Include
    [AI Gateway]
    prefix for easy filtering
  5. Mock in tests - Avoid real API calls in unit tests:
    typescript
    vi.mock('ai', () => ({
      generateText: vi.fn().mockRejectedValue(new Error('Mocked')),
    }))

  1. 始终处理错误 - AI调用可能失败;要有降级行为
  2. 不要过度设计 - 简单模式即可工作;避免使用自定义提供者
  3. 先在本地测试 - 使用
    AI_GATEWAY_API_KEY
    vercel dev
  4. 记录错误 - 包含
    [AI Gateway]
    前缀以便过滤
  5. 在测试中模拟 - 单元测试中避免真实API调用:
    typescript
    vi.mock('ai', () => ({
      generateText: vi.fn().mockRejectedValue(new Error('Mocked')),
    }))

Build Caching

构建缓存

Turborepo Remote Cache

Turborepo 远程缓存

bash
undefined
bash
undefined

Link 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:
  1. Dashboard: Settings → General → Build Cache → Purge
  2. CLI: Redeploy with
    vercel --force

如果构建结果过期或失败:
  1. 控制台: 设置 → 常规 → 构建缓存 → 清除
  2. 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:
nextjs-e2e-testing.md
skill for complete E2E testing patterns.

当针对同时支持本地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.md
技能文档,获取完整的端到端测试模式。

Common Issues

常见问题

Build Timeout

构建超时

Error:
Build exceeded maximum duration
Fixes:
  • Upgrade plan (Hobby: 45min, Pro: 45min)
  • Optimize build (parallel builds, caching)
  • Use
    turbo prune
    for monorepos
错误:
Build exceeded maximum duration
解决方法:
  • 升级套餐(免费版:45分钟,专业版:45分钟)
  • 优化构建(并行构建、缓存)
  • 对单仓库多项目使用
    turbo prune

Memory Exceeded

内存不足

Error:
FATAL ERROR: JavaScript heap out of memory
Fix: 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
    devDependencies
    but needed at runtime
  • Case sensitivity (works on Mac, fails on Linux)
  • Missing from
    package.json
Fix: Move to
dependencies
or check case sensitivity.

错误:
Cannot find module 'x'
原因:
  • 依赖项在
    devDependencies
    中,但运行时需要
  • 大小写问题(在Mac上正常,在Linux上失败)
  • package.json
    中缺少该依赖
解决方法: 将依赖移到
dependencies
或检查大小写一致性。

OAuth Integration

OAuth 集成

Callback URL Must Match Final Domain

回调URL必须匹配最终域名

If your domain redirects (e.g.,
example.com
www.example.com
), the OAuth callback URL must use the final destination domain:
https://www.example.com/api/auth/callback  ✓
https://example.com/api/auth/callback      ✗ (if it redirects to www)
如果你的域名会重定向(例如
example.com
www.example.com
),OAuth回调URL必须使用最终目标域名
https://www.example.com/api/auth/callback  ✓
https://example.com/api/auth/callback      ✗(如果重定向到www)

Debugging OAuth Issues

调试OAuth问题

bash
undefined
bash
undefined

Check 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
aud: "https://vercel.com/{team-slug}"
but GCP providers often default to expecting
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令牌的
aud
"https://vercel.com/{team-slug}"
,但GCP提供者通常默认期望
"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
@vercel/functions/oidc
, NOT the deprecated
@vercel/oidc
:
typescript
// ❌ Old (deprecated, causes "getToken is not a function")
import { getToken } from '@vercel/oidc'

// ✅ New
import { getVercelOidcToken } from '@vercel/functions/oidc'
使用
@vercel/functions/oidc
,不要使用已弃用的
@vercel/oidc
typescript
// ❌ 旧版(已弃用,会导致"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
    \n
    in the
    stsAudience
    field
  • STS exchange fails with "Invalid value for audience"
Fix: Re-add each WIF env var using
printf
(not dashboard copy-paste):
bash
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"失败
解决方法: 使用
printf
重新添加每个WIF环境变量(不要从控制台复制粘贴):
bash
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

Domains

域名

Add Custom Domain

添加自定义域名

bash
vercel domains add example.com
bash
vercel domains add example.com

DNS Configuration

DNS 配置

TypeNameValue
A@76.76.21.21
CNAMEwwwcname.vercel-dns.com
类型名称
A@76.76.21.21
CNAMEwwwcname.vercel-dns.com

SSL

SSL

Automatic via Let's Encrypt. No configuration needed.

通过Let's Encrypt自动配置。无需手动操作。

Quick Reference

快速参考

bash
undefined
bash
undefined

Deploy 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

时长限制

PlanWithout Fluid ComputeWith Fluid Compute
Hobby10s default, 60s max300s default, 300s max
Pro15s default, 300s max300s default, 800s max
Enterprise15s default, 300s max300s default, 800s max
<EXTREMELY-IMPORTANT> If your function times out at exactly 60 seconds despite Pro plan and Fluid Compute settings, Fluid Compute is NOT actually active. This is a known issue affecting multiple users. </EXTREMELY-IMPORTANT>
套餐未启用Fluid Compute已启用Fluid Compute
免费版默认10秒,最大60秒默认300秒,最大300秒
专业版默认15秒,最大300秒默认300秒,最大800秒
企业版默认15秒,最大300秒默认300秒,最大800秒
<EXTREMELY-IMPORTANT> 如果你使用专业版且已配置Fluid Compute,但函数仍在60秒时超时,说明Fluid Compute并未实际激活。这是一个影响多个用户的已知问题。 </EXTREMELY-IMPORTANT>

Enabling Fluid Compute

启用Fluid Compute

Two independent settings must BOTH be configured:
  1. Dashboard Toggle (Project-wide)
    • Go to Project Settings → Functions
    • Find "Fluid Compute" section
    • Toggle ON
    • Click Save
    • Redeploy (changes only apply to new deployments)
  2. Dashboard Max Duration (Project-wide)
    • Go to Project Settings → Functions
    • Find "Function Max Duration" section
    • Set to desired value (e.g., 300)
    • Click Save
  3. vercel.json (Per-deployment override)
    json
    {
      "$schema": "https://openapi.vercel.sh/vercel.json",
      "fluid": true
    }
    Note:
    "fluid": true
    in vercel.json only enables Fluid Compute for that specific deployment, NOT project-wide.
必须同时配置两个独立设置:
  1. 控制台开关(项目级)
    • 进入项目设置 → 函数
    • 找到“Fluid Compute”部分
    • 开启开关
    • 点击保存
    • 重新部署(更改仅对新部署生效)
  2. 控制台最大运行时长(项目级)
    • 进入项目设置 → 函数
    • 找到“函数最大运行时长”部分
    • 设置为所需值(例如300)
    • 点击保存
  3. vercel.json(部署级覆盖)
    json
    {
      "$schema": "https://openapi.vercel.sh/vercel.json",
      "fluid": true
    }
    注意:vercel.json中的
    "fluid": true
    仅对该特定部署启用Fluid Compute,而非项目级全局启用。

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 StructureCorrect PatternWrong Pattern
src/app/api/
src/app/api/**/route.ts
app/api/**/route.ts
app/api/
app/api/**/route.ts
src/app/api/**/route.ts
Pages Router
src/pages/api/**/*.ts
pages/api/**/*.ts
For App Router specifically:
  • Use
    **/route.ts
    pattern, not
    **/*.ts
  • The pattern must match the actual route handler files
<EXTREMELY-IMPORTANT> 如果项目使用`src/`目录,路径模式中必须包含`src/`。 </EXTREMELY-IMPORTANT>
项目结构正确模式错误模式
src/app/api/
src/app/api/**/route.ts
app/api/**/route.ts
app/api/
app/api/**/route.ts
src/app/api/**/route.ts
Pages Router
src/pages/api/**/*.ts
pages/api/**/*.ts
对于App Router:
  • 使用
    **/route.ts
    模式,而非
    **/*.ts
  • 模式必须与实际路由处理文件匹配

Known Issue: 60s Timeout Despite Pro Plan

已知问题:专业版仍出现60秒超时

Symptoms:
  • Pro plan confirmed
  • "fluid": true
    in vercel.json
  • maxDuration = 300
    in route.ts
  • Function still times out at exactly 60 seconds
Root Cause: Fluid Compute is not being applied at the platform level despite settings.
Community Reports:
Troubleshooting Checklist:
  1. ✅ Verify Dashboard toggle is ON (not just vercel.json)
  2. ✅ Verify Dashboard "Function Max Duration" is set
  3. ✅ Verify path pattern matches your file structure
  4. ✅ Redeploy after any settings change
  5. ✅ Check if using Node.js runtime (Edge has different limits)
  6. ❌ 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。
社区反馈:
故障排除清单:
  1. ✅ 确认控制台开关已开启(不只是vercel.json中配置)
  2. ✅ 确认控制台“函数最大运行时长”已设置
  3. ✅ 确认路径模式与文件结构匹配
  4. ✅ 更改设置后重新部署
  5. ✅ 检查是否使用Node.js运行时(Edge函数有不同限制)
  6. ❌ 如果以上都正确 → 联系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
    Authorization: Bearer <CRON_SECRET>
    header
  • Set
    CRON_SECRET
    env var to protect endpoint
  • 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
undefined
bash
undefined

Use 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"
undefined
vercel curl /api/cron/my-job -- --header "Authorization: Bearer $CRON_SECRET"
undefined

Runtime 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

Resources

资源