PlanetScale 与 Vercel 集成 #
本章将介绍如何将 PlanetScale 数据库与 Vercel 部署平台集成,构建完整的 Serverless 应用。
Vercel 与 PlanetScale #
为什么选择这个组合? #
text
┌─────────────────────────────────────────────────────────────┐
│ 完美组合 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Vercel 优势: │
│ ├── 全球边缘网络 │
│ ├── 自动 CI/CD │
│ ├── 预览部署 │
│ └── Serverless Functions │
│ │
│ PlanetScale 优势: │
│ ├── 无服务器 MySQL │
│ ├── 自动扩展 │
│ ├── 数据库分支 │
│ └── 零停机 Schema 变更 │
│ │
│ 组合优势: │
│ ✅ 完全无服务器架构 │
│ ✅ 全球低延迟 │
│ ✅ 简化开发流程 │
│ ✅ 高可用性 │
│ │
└─────────────────────────────────────────────────────────────┘
自动集成 #
集成步骤 #
text
┌─────────────────────────────────────────────────────────────┐
│ 自动集成流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 步骤 1:在 PlanetScale 添加集成 │
│ 1. 登录 PlanetScale 控制台 │
│ 2. 进入数据库 Settings → Integrations │
│ 3. 点击 "Add integration" │
│ 4. 选择 "Vercel" │
│ 5. 授权连接 Vercel 账号 │
│ │
│ 步骤 2:选择项目 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Select Vercel project: │ │
│ │ ○ my-nextjs-app │ │
│ │ ○ my-api-server │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 步骤 3:配置分支映射 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Production branch: main │ │
│ │ Preview branch: preview │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 步骤 4:自动配置完成 │
│ - DATABASE_URL 自动添加到 Vercel │
│ - 每次部署自动更新连接 │
│ │
└─────────────────────────────────────────────────────────────┘
手动配置 #
获取连接字符串 #
bash
# 创建密码
pscale password create my-database main vercel-production
# 输出连接字符串
# mysql://abc123:pscale_pw_xxx@aws.connect.psdb.cloud/my-database?sslaccept=strict
在 Vercel 添加环境变量 #
bash
# 使用 Vercel CLI
vercel env add DATABASE_URL production
# 输入连接字符串
mysql://abc123:pscale_pw_xxx@aws.connect.psdb.cloud/my-database?sslaccept=strict
# 为预览环境添加
vercel env add DATABASE_URL preview
Vercel Dashboard 配置 #
text
┌─────────────────────────────────────────────────────────────┐
│ 环境变量配置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Settings → Environment Variables │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Name Value Environments│
│ ├─────────────────────────────────────────────────────┤ │
│ │ DATABASE_URL mysql://... Production │ │
│ │ DATABASE_URL mysql://... Preview │ │
│ │ DATABASE_URL mysql://... Development│ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ [+ Add] [Edit] [Remove] │
│ │
└─────────────────────────────────────────────────────────────┘
Next.js 项目示例 #
项目结构 #
text
my-app/
├── app/
│ ├── api/
│ │ └── users/
│ │ └── route.ts
│ ├── users/
│ │ └── page.tsx
│ └── layout.tsx
├── lib/
│ └── db.ts
├── prisma/
│ └── schema.prisma
├── package.json
└── vercel.json
数据库连接 #
typescript
import { connect } from '@planetscale/database';
export function getConnection() {
const config = {
url: process.env.DATABASE_URL
};
return connect(config);
}
API Route #
typescript
import { getConnection } from '@/lib/db';
import { NextResponse } from 'next/server';
export async function GET() {
const conn = getConnection();
const result = await conn.execute('SELECT * FROM users LIMIT 10');
return NextResponse.json(result.rows);
}
export async function POST(request: Request) {
const conn = getConnection();
const body = await request.json();
const result = await conn.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
[body.name, body.email]
);
return NextResponse.json({ id: result.insertId, ...body }, { status: 201 });
}
Server Component #
typescript
import { getConnection } from '@/lib/db';
export default async function UsersPage() {
const conn = getConnection();
const result = await conn.execute('SELECT * FROM users LIMIT 10');
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">Users</h1>
<ul className="space-y-2">
{result.rows.map((user: any) => (
<li key={user.id} className="p-4 bg-gray-100 rounded">
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}
Edge Runtime 支持 #
配置 Edge Runtime #
typescript
import { connect } from '@planetscale/database';
export const runtime = 'edge';
export async function GET() {
const conn = connect({
url: process.env.DATABASE_URL,
fetch: (url, init) => {
return fetch(url, init);
}
});
const result = await conn.execute('SELECT * FROM users LIMIT 10');
return Response.json(result.rows);
}
Edge 注意事项 #
text
┌─────────────────────────────────────────────────────────────┐
│ Edge Runtime 限制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ✅ 支持: │
│ - @planetscale/database │
│ - HTTP-based 连接 │
│ - 简单查询 │
│ │
│ ❌ 不支持: │
│ - mysql2(需要 Node.js 运行时) │
│ - 传统 TCP 连接 │
│ - 连接池 │
│ │
│ 建议: │
│ - 使用 @planetscale/database │
│ - 保持查询简单 │
│ - 避免复杂事务 │
│ │
└─────────────────────────────────────────────────────────────┘
预览部署 #
自动创建预览分支 #
yaml
# .github/workflows/preview.yml
name: Preview Deployment
on:
pull_request:
branches: [main]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install PlanetScale CLI
run: curl -fsSL https://raw.githubusercontent.com/planetscale/cli/main/install.sh | bash
- name: Create preview branch
env:
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
run: |
pscale auth login --service-token $PLANETSCALE_SERVICE_TOKEN
pscale branch create my-app pr-${{ github.event.pull_request.number }} --from main || true
- name: Create password for preview
run: |
pscale password create my-app pr-${{ github.event.pull_request.number }} preview --ttl 72h
Vercel 预览环境 #
text
┌─────────────────────────────────────────────────────────────┐
│ 预览环境配置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 每个预览部署: │
│ ├── 独立的数据库分支 │
│ ├── 独立的连接密码 │
│ └── 自动过期 │
│ │
│ 分支命名: │
│ pr-123 → 对应 PR #123 │
│ │
│ 环境变量: │
│ DATABASE_URL=mysql://...@.../my-app?sslaccept=strict │
│ │
└─────────────────────────────────────────────────────────────┘
部署最佳实践 #
环境变量管理 #
javascript
// vercel.json
{
"env": {
"DATABASE_URL": "@database_url"
}
}
健康检查 #
typescript
import { getConnection } from '@/lib/db';
import { NextResponse } from 'next/server';
export async function GET() {
try {
const conn = getConnection();
await conn.execute('SELECT 1');
return NextResponse.json({ status: 'healthy' });
} catch (error) {
return NextResponse.json(
{ status: 'unhealthy', error: String(error) },
{ status: 500 }
);
}
}
错误处理 #
typescript
import { getConnection } from '@/lib/db';
import { NextResponse } from 'next/server';
export async function GET() {
try {
const conn = getConnection();
const result = await conn.execute('SELECT * FROM users');
return NextResponse.json(result.rows);
} catch (error) {
console.error('Database error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
下一步 #
现在你已经掌握了 Vercel 集成,接下来学习 生产实践,了解生产环境的最佳实践!
最后更新:2026-03-29