Serverless Functions #

Serverless 概述 #

Serverless Functions 是按需执行的后端函数:

text
┌──────────────────────────────────────────────────────┐
│                                                      │
│  传统服务器                                          │
│  ┌─────────┐                                         │
│  │ 服务器  │  →  24/7 运行,持续计费                 │
│  └─────────┘                                         │
│                                                      │
│  Serverless                                         │
│  ┌─────────┐                                         │
│  │  函数   │  →  按需执行,按使用计费                 │
│  └─────────┘                                         │
│                                                      │
└──────────────────────────────────────────────────────┘

特点 #

特点 说明
无服务器管理 无需配置和维护服务器
自动扩展 根据请求量自动扩展
按需计费 只为实际执行时间付费
快速部署 代码更新即时生效

创建函数 #

Next.js API Routes #

text
app/
└── api/
    ├── hello/
    │   └── route.ts
    └── users/
        └── [id]/
            └── route.ts

基本 API 路由 #

typescript
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  return NextResponse.json({ message: 'Hello World' })
}

export async function POST(request: NextRequest) {
  const body = await request.json()
  return NextResponse.json({ received: body })
}

Pages Router #

typescript
import type { NextApiRequest, NextApiResponse } from 'next'

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'GET') {
    res.status(200).json({ message: 'Hello World' })
  } else if (req.method === 'POST') {
    res.status(201).json({ received: req.body })
  } else {
    res.status(405).json({ error: 'Method not allowed' })
  }
}

独立函数文件 #

text
api/
├── hello.js
└── users.js
javascript
export default function handler(req, res) {
  res.status(200).json({ message: 'Hello World' })
}

HTTP 方法 #

支持的方法 #

typescript
export async function GET(request: NextRequest) {
  return NextResponse.json({ method: 'GET' })
}

export async function POST(request: NextRequest) {
  return NextResponse.json({ method: 'POST' })
}

export async function PUT(request: NextRequest) {
  return NextResponse.json({ method: 'PUT' })
}

export async function DELETE(request: NextRequest) {
  return NextResponse.json({ method: 'DELETE' })
}

export async function PATCH(request: NextRequest) {
  return NextResponse.json({ method: 'PATCH' })
}

export async function HEAD(request: NextRequest) {
  return new NextResponse(null, { status: 200 })
}

export async function OPTIONS(request: NextRequest) {
  return new NextResponse(null, {
    status: 200,
    headers: {
      'Allow': 'GET, POST, PUT, DELETE, OPTIONS',
    },
  })
}

请求处理 #

获取请求参数 #

typescript
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams
  const name = searchParams.get('name')
  const page = searchParams.get('page') || '1'
  
  return NextResponse.json({ name, page })
}

获取动态路由参数 #

typescript
import { NextRequest, NextResponse } from 'next/server'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const id = params.id
  
  return NextResponse.json({ id })
}

获取请求体 #

typescript
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  const json = await request.json()
  const text = await request.text()
  const formData = await request.formData()
  
  return NextResponse.json({ json })
}

获取请求头 #

typescript
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const authHeader = request.headers.get('authorization')
  const userAgent = request.headers.get('user-agent')
  
  return NextResponse.json({ authHeader, userAgent })
}

响应处理 #

JSON 响应 #

typescript
return NextResponse.json(
  { message: 'Success', data: { id: 1 } },
  { status: 200 }
)

自定义响应头 #

typescript
return NextResponse.json(
  { message: 'Success' },
  {
    status: 200,
    headers: {
      'X-Custom-Header': 'value',
      'Cache-Control': 'max-age=3600',
    },
  }
)

重定向 #

typescript
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  return NextResponse.redirect(new URL('/login', request.url))
}
typescript
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  const response = NextResponse.json({ message: 'Success' })
  
  response.cookies.set('token', 'abc123', {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 60 * 60 * 24 * 7,
  })
  
  return response
}

export async function DELETE(request: NextRequest) {
  const response = NextResponse.json({ message: 'Logged out' })
  
  response.cookies.delete('token')
  
  return response
}

函数配置 #

运行时配置 #

typescript
export const runtime = 'nodejs'
export const runtime = 'edge'

执行时间限制 #

typescript
export const maxDuration = 60
计划 最大执行时间
Hobby 10 秒
Pro 60 秒
Enterprise 900 秒

内存配置 #

json
{
  "functions": {
    "api/**/*.js": {
      "memory": 1024,
      "maxDuration": 10
    }
  }
}

区域配置 #

json
{
  "functions": {
    "api/**/*.js": {
      "memory": 1024,
      "maxDuration": 10,
      "regions": ["hkg1", "sfo1"]
    }
  }
}

数据库连接 #

Prisma 连接 #

typescript
import { PrismaClient } from '@prisma/client'

const globalForPrisma = global as unknown as { prisma: PrismaClient }

export const prisma =
  globalForPrisma.prisma ||
  new PrismaClient({
    log: ['query'],
  })

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

API 路由中使用 #

typescript
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'

export async function GET() {
  const users = await prisma.user.findMany()
  return NextResponse.json(users)
}

export async function POST(request: NextRequest) {
  const body = await request.json()
  const user = await prisma.user.create({
    data: body,
  })
  return NextResponse.json(user)
}

连接池配置 #

typescript
import { Pool } from 'pg'

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 1,
})

export async function query(text: string, params?: any[]) {
  const client = await pool.connect()
  try {
    return await client.query(text, params)
  } finally {
    client.release()
  }
}

文件上传 #

处理文件上传 #

typescript
import { NextRequest, NextResponse } from 'next/server'
import { writeFile } from 'fs/promises'
import path from 'path'

export async function POST(request: NextRequest) {
  const formData = await request.formData()
  const file = formData.get('file') as File
  
  if (!file) {
    return NextResponse.json({ error: 'No file uploaded' }, { status: 400 })
  }
  
  const bytes = await file.arrayBuffer()
  const buffer = Buffer.from(bytes)
  
  const filePath = path.join('/tmp', file.name)
  await writeFile(filePath, buffer)
  
  return NextResponse.json({ 
    message: 'File uploaded',
    filename: file.name,
    size: file.size,
  })
}

上传到云存储 #

typescript
import { NextRequest, NextResponse } from 'next/server'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'

const s3 = new S3Client({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
})

export async function POST(request: NextRequest) {
  const formData = await request.formData()
  const file = formData.get('file') as File
  
  const bytes = await file.arrayBuffer()
  const buffer = Buffer.from(bytes)
  
  await s3.send(new PutObjectCommand({
    Bucket: process.env.S3_BUCKET!,
    Key: file.name,
    Body: buffer,
    ContentType: file.type,
  }))
  
  return NextResponse.json({ 
    url: `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${file.name}`
  })
}

错误处理 #

统一错误处理 #

typescript
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  try {
    const data = await fetchData()
    return NextResponse.json(data)
  } catch (error) {
    console.error('API Error:', error)
    
    if (error instanceof NotFoundError) {
      return NextResponse.json(
        { error: 'Resource not found' },
        { status: 404 }
      )
    }
    
    return NextResponse.json(
      { error: 'Internal Server Error' },
      { status: 500 }
    )
  }
}

自定义错误类 #

typescript
class APIError extends Error {
  constructor(
    public message: string,
    public statusCode: number = 500
  ) {
    super(message)
  }
}

class NotFoundError extends APIError {
  constructor(message: string = 'Not Found') {
    super(message, 404)
  }
}

class ValidationError extends APIError {
  constructor(message: string) {
    super(message, 400)
  }
}

日志与调试 #

控制台日志 #

typescript
export async function GET() {
  console.log('Function executed')
  console.error('Error occurred')
  console.warn('Warning message')
  console.info('Info message')
  
  return Response.json({ status: 'ok' })
}

查看日志 #

bash
vercel logs --follow

结构化日志 #

typescript
export async function GET() {
  console.log(JSON.stringify({
    level: 'info',
    message: 'Request received',
    timestamp: new Date().toISOString(),
    requestId: crypto.randomUUID(),
  }))
  
  return Response.json({ status: 'ok' })
}

最佳实践 #

函数设计原则 #

text
┌─────────────────────────────────────────┐
│        Serverless 最佳实践               │
├─────────────────────────────────────────┤
│  ✓ 单一职责                             │
│  ✓ 无状态设计                           │
│  ✓ 快速响应                             │
│  ✓ 合理的错误处理                       │
│  ✓ 使用环境变量                         │
│  ✓ 连接复用                             │
└─────────────────────────────────────────┘

冷启动优化 #

typescript
let cachedData: any = null

export async function GET() {
  if (cachedData) {
    return Response.json(cachedData)
  }
  
  cachedData = await fetchExpensiveData()
  return Response.json(cachedData)
}

下一步 #

了解 Serverless Functions 后,接下来学习 图片优化 探索 Vercel 的图片处理能力!

最后更新:2026-03-28