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))
}
Cookie 操作 #
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