Vercel Functions 完整教學|2025 Serverless 入門指南

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 執行超時怎麼辦?

  1. 優化程式碼,減少執行時間
  2. 升級到 Pro 方案(60 秒限制)
  3. 使用非同步處理

Q2:冷啟動很慢怎麼辦?

  1. 使用 Edge Runtime(如果可以)
  2. 減少依賴套件
  3. 考慮使用 warming 服務

Q3:怎麼存取環境變數?

const apiKey = process.env.API_KEY;

記得在 Vercel Dashboard 設定環境變數。

Q4:可以用 WebSocket 嗎?

Serverless Functions 不支援持久連線。

考慮使用:
- Vercel Edge Functions(有限支援)
- 外部服務(Pusher、Socket.io)


Vercel 部署失敗?

Build Error、環境變數、自訂網域,我們幫你快速排除問題。

解決 Vercel 問題


延伸閱讀

分享文章:
V

VibeFix

專門解決 AI Vibe Coding 後的疑難雜症,讓你的專案順利上線。

這篇文章有幫到你嗎?

如果還有問題,讓我們直接幫你解決!

聯繫我們