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