Vercel Functions 完整教學|2025 Serverless 入門指南
Vercel 不只能部署前端。
用 Serverless Functions,你可以執行後端程式碼。
不用管伺服器,不用擔心擴展,用多少付多少。
這篇文章帶你完整了解 Vercel Functions。
什麼是 Serverless Functions?
Serverless Functions 是無需管理伺服器的後端程式碼執行方式。
傳統伺服器 vs Serverless
| 項目 | 傳統伺服器 | Serverless |
|---|---|---|
| 伺服器管理 | 需要自己管理 | 平台管理 |
| 擴展 | 手動設定 | 自動擴展 |
| 計費 | 24/7 付費 | 用多少付多少 |
| 冷啟動 | 無 | 有(首次請求較慢) |
| 執行時間 | 無限制 | 有限制 |
Vercel Functions 的特點
- 自動擴展: 流量大時自動增加運算資源
- 全球部署: 在最近的節點執行
- 簡單設定: 放對位置就能用
- 多語言支援: Node.js、Go、Python、Ruby
兩種 Function 類型
| 類型 | Serverless Functions | Edge Functions |
|---|---|---|
| 執行環境 | Node.js | V8 Runtime |
| 冷啟動 | 200-500ms | 幾乎為零 |
| 執行時間 | 10s-900s | 30s |
| API 支援 | 完整 Node.js | 有限(Web APIs) |
| 適合場景 | 複雜運算 | 簡單邏輯、低延遲 |
快速開始
建立第一個 Function
Next.js App Router:
// app/api/hello/route.ts
export async function GET() {
return Response.json({ message: 'Hello, World!' });
}
Next.js Pages Router:
// pages/api/hello.ts
export default function handler(req, res) {
res.status(200).json({ message: 'Hello, World!' });
}
測試
# 本機開發
npm run dev
# 訪問
curl http://localhost:3000/api/hello
# {"message":"Hello, World!"}
部署
git push origin main
# Vercel 自動部署
# 訪問
curl https://your-app.vercel.app/api/hello
HTTP 方法處理
App Router(推薦)
// app/api/users/route.ts
// GET 請求
export async function GET(request: Request) {
const users = await db.users.findMany();
return Response.json(users);
}
// POST 請求
export async function POST(request: Request) {
const body = await request.json();
const user = await db.users.create({ data: body });
return Response.json(user, { status: 201 });
}
// PUT 請求
export async function PUT(request: Request) {
const body = await request.json();
const user = await db.users.update({
where: { id: body.id },
data: body,
});
return Response.json(user);
}
// DELETE 請求
export async function DELETE(request: Request) {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
await db.users.delete({ where: { id } });
return Response.json({ success: true });
}
Pages Router
// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
switch (req.method) {
case 'GET':
const users = await db.users.findMany();
return res.json(users);
case 'POST':
const user = await db.users.create({ data: req.body });
return res.status(201).json(user);
default:
res.setHeader('Allow', ['GET', 'POST']);
return res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
動態路由
App Router
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const user = await db.users.findUnique({
where: { id: params.id },
});
if (!user) {
return Response.json(
{ error: 'User not found' },
{ status: 404 }
);
}
return Response.json(user);
}
取得 Query Parameters
// app/api/search/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const query = searchParams.get('q');
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '10');
const results = await db.products.findMany({
where: { name: { contains: query } },
skip: (page - 1) * limit,
take: limit,
});
return Response.json({ results, page, limit });
}
取得 Request Body
// app/api/contact/route.ts
export async function POST(request: Request) {
// JSON body
const body = await request.json();
const { name, email, message } = body;
// Form data
// const formData = await request.formData();
// const name = formData.get('name');
// 處理...
return Response.json({ success: true });
}
實用範例
範例一:API 代理
// app/api/weather/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const city = searchParams.get('city');
const res = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${process.env.WEATHER_API_KEY}`
);
const data = await res.json();
return Response.json(data);
}
範例二:表單處理
// app/api/contact/route.ts
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST(request: Request) {
const { name, email, message } = await request.json();
// 驗證
if (!name || !email || !message) {
return Response.json(
{ error: '所有欄位都是必填' },
{ status: 400 }
);
}
// 發送郵件
await resend.emails.send({
from: '[email protected]',
to: '[email protected]',
subject: `來自 ${name} 的聯繫`,
text: `Email: ${email}\n\n${message}`,
});
return Response.json({ success: true });
}
範例三:資料庫 CRUD
// app/api/posts/route.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function GET() {
const posts = await prisma.post.findMany({
orderBy: { createdAt: 'desc' },
include: { author: true },
});
return Response.json(posts);
}
export async function POST(request: Request) {
const { title, content, authorId } = await request.json();
const post = await prisma.post.create({
data: { title, content, authorId },
});
return Response.json(post, { status: 201 });
}
範例四:驗證 Webhook
// app/api/webhook/route.ts
import crypto from 'crypto';
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get('x-signature');
// 驗證簽名
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET!)
.update(body)
.digest('hex');
if (signature !== expectedSignature) {
return Response.json(
{ error: 'Invalid signature' },
{ status: 401 }
);
}
const data = JSON.parse(body);
// 處理 webhook...
return Response.json({ received: true });
}
設定和優化
設定執行時間
// app/api/long-task/route.ts
export const maxDuration = 60; // 60 秒(需要 Pro 方案)
export async function POST(request: Request) {
// 長時間任務
await processLongTask();
return Response.json({ success: true });
}
設定記憶體
// app/api/heavy-task/route.ts
export const memory = 1024; // 1024 MB
export async function POST(request: Request) {
// 記憶體密集任務
}
使用 Edge Runtime
// app/api/fast/route.ts
export const runtime = 'edge';
export async function GET() {
return Response.json({ fast: true });
}
連接資料庫
使用 Prisma
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
// app/api/users/route.ts
import { prisma } from '@/lib/prisma';
export async function GET() {
const users = await prisma.user.findMany();
return Response.json(users);
}
注意事項
連線池問題:
Serverless 環境每次請求可能是新的實例,要注意連線池設定。
# .env
DATABASE_URL="postgresql://...?connection_limit=1"
冷啟動影響:
第一次請求需要建立連線,可能較慢。
錯誤處理
基本錯誤處理
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
try {
const user = await prisma.user.findUnique({
where: { id: params.id },
});
if (!user) {
return Response.json(
{ error: 'User not found' },
{ status: 404 }
);
}
return Response.json(user);
} catch (error) {
console.error('Database error:', error);
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
自訂錯誤類別
// lib/errors.ts
export class AppError extends Error {
constructor(
public statusCode: number,
message: string
) {
super(message);
}
}
// app/api/users/route.ts
export async function POST(request: Request) {
try {
const body = await request.json();
if (!body.email) {
throw new AppError(400, 'Email is required');
}
const user = await createUser(body);
return Response.json(user);
} catch (error) {
if (error instanceof AppError) {
return Response.json(
{ error: error.message },
{ status: error.statusCode }
);
}
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
安全性
驗證請求
// app/api/protected/route.ts
import { auth } from '@/auth';
export async function GET(request: Request) {
const session = await auth();
if (!session) {
return Response.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// 繼續處理...
return Response.json({ data: 'protected data' });
}
CORS 設定
// app/api/public/route.ts
export async function GET() {
return Response.json(
{ data: 'public data' },
{
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
}
);
}
export async function OPTIONS() {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
常見問題 FAQ
Q1:Function 執行超時怎麼辦?
- 優化程式碼,減少執行時間
- 升級到 Pro 方案(60 秒限制)
- 使用非同步處理
Q2:冷啟動很慢怎麼辦?
- 使用 Edge Runtime(如果可以)
- 減少依賴套件
- 考慮使用 warming 服務
Q3:怎麼存取環境變數?
const apiKey = process.env.API_KEY;
記得在 Vercel Dashboard 設定環境變數。
Q4:可以用 WebSocket 嗎?
Serverless Functions 不支援持久連線。
考慮使用:
- Vercel Edge Functions(有限支援)
- 外部服務(Pusher、Socket.io)
Vercel 部署失敗?
Build Error、環境變數、自訂網域,我們幫你快速排除問題。