Skip to main content

Migrate from Vercel

Migrate projects deployed on Vercel (static sites, Next.js, Serverless Functions, etc.) to Tencent CloudBase

Migration Overview

Vercel and CloudBase are highly aligned in core capabilities — both offer frontend + serverless backend + data storage as a one-stop deployment experience. Migration focuses on adapting six modules: static hosting, backend compute (Cloud Functions & CloudRun), databases, file storage, authentication, and environment variables.

DimensionVercelCloudBase
Static HostingAuto build & deploy from GitStatic website hosting + CDN acceleration
Serverless FunctionsVercel Functions (Node/Go/Py/Ruby)Cloud Functions (Node.js / PHP / Python / Golang / Java) + CloudBase Run (any language/framework)
Edge FunctionsEdge Functions / MiddlewareCloudRun Function Framework (supports SSE, WebSocket)
DatabaseVercel Marketplace (Neon Postgres / Supabase, etc.)Document Database (NoSQL) + MySQL Database + PostgreSQL Database
Object StorageVercel Blob StorageCloud Storage (built-in CDN + image processing)
AuthenticationThird-party integrations (Clerk / Auth0 / NextAuth)Built-in authentication (WeChat / SMS / Email / Anonymous / Custom)
Custom DomainOne-click binding from DashboardConsole binding + self-upload SSL certificate
Environment VariablesDashboard / CLI managementConsole / CLI management

Quick Reference

My current Vercel usageCloudBase equivalent
Deploy static sites (Vite/React/Vue)tcb app deploy -e <envId>
Deploy Next.js SSRCloudBase Run + Dockerfile
Deploy Next.js static exportSame as static sites
api/*.ts Serverless FunctionsCloud Functions or CloudBase Run Express app
middleware.ts edge logicCloudBase Run middleware / API Gateway
Environment variables process.env.XXXConfigure in console, no code changes needed
Custom domainsConsole "Domain Management" binding
Vercel Postgres (Marketplace · Neon)CloudBase PostgreSQL Database / MySQL Database
Vercel Blob StorageCloudBase Cloud Storage

Module 1: Static Hosting

CloudBase Static Website Hosting: built-in CDN, HTTPS, SPA Fallback, HTTP response headers, routing rules. Console: Static Website Hosting.

1. Pure Static Sites (Vite / Vue / React / Svelte / Astro / Hugo)

Applicable when: build output is pure HTML/CSS/JS, no dependency on Vercel Functions

# Install CLI
npm install -g @cloudbase/cli

# Login
tcb login

# One-click build + deploy (auto-detects framework, runs install → build → upload → route binding)
tcb app deploy -e <envId>

# Or specify framework type
tcb app deploy --framework vite -e <envId>

# Deploy to sub-path /app
tcb app deploy --deploy-path /app -e <envId>

A single command replaces Vercel's Git auto-deployment. tcb hosting deploy is only for uploading pre-built static files and is not the standard app deployment path.

2. Static Framework Deployment (Next.js / Nuxt.js / SvelteKit / Astro)

If using the framework's static export mode (build output is pure HTML/CSS/JS):

FrameworkStatic Export ConfigurationDeploy Command
Next.jsSet output: 'export' in next.config.jstcb app deploy --framework next --output-dir out -e <envId>
Nuxt.jsSet ssr: false in nuxt.config.tstcb app deploy -e <envId>
SvelteKitConfigure @sveltejs/adapter-static in svelte.config.jstcb app deploy -e <envId>
AstroDefault is static (output: 'static')tcb app deploy -e <envId>

tcb app deploy auto-detects framework type and runs install → build → upload → route binding. Next.js requires explicit output directory specification; other frameworks are auto-detected.


Module 2: Backend Compute (Cloud Functions & CloudBase Run)

Vercel Functions alternatives come in two forms: lightweight standalone APIs use Cloud Functions, multi-route / SSR framework scenarios use CloudBase Run. Choose based on complexity.

Scenario Overview

Vercel UsageCloudBase SolutionBest For
A few standalone files under api/Cloud FunctionsFew APIs, independent deployment per function, minimal changes
5+ files under api/ or need route managementCloudBase Run (Express/Koa)Avoid function fragmentation, unified routing and middleware
Next.js / Nuxt / SvelteKit / AstroCloudBase Run (Docker)Framework-native SSR and server routes need container environment

1. Scenario 1: Pure API Routes → Cloud Functions

CloudBase Cloud Functions: Node.js / PHP / Python / Golang / Java runtimes, HTTP/timer/database triggers, max 900s timeout. Console: Cloud Functions.

Capability Comparison

DimensionVercel FunctionsCloudBase Cloud Functions
RuntimeNode.js / Go / Python / RubyNode.js / PHP / Python / Golang / Java
TriggerHTTP path auto-routingHTTP trigger / Timer trigger / Database trigger
Path Mappingapi/*.ts/api/*One path per function
Timeout10s (Hobby) / 60s (Pro)Up to 900s
Memory128MB - 3008MB256MB - 3072MB
Cold StartFast (edge deployment)Configurable keep-alive (minNum >= 1)

Directory Structure Migration

Vercel project structure:

api/
hello.ts → /api/hello
users/[id].ts → /api/users/:id
webhook.ts → /api/webhook

CloudBase Cloud Functions structure:

cloudfunctions/
hello/
index.js
package.json
users/
index.js
package.json

Tip: If you have many files under api/ but not yet ready to consolidate into CloudBase Run, use the batch-migrate-functions.js script in the "Migration Script Tools" section at the end to generate the above skeleton with one command.

Code Example

Vercel (api/hello.ts):

// api/hello.ts
import type { VercelRequest, VercelResponse } from '@vercel/node'

export default function handler(req: VercelRequest, res: VercelResponse) {
const { name = 'World' } = req.query
res.status(200).json({ message: `Hello, ${name}!` })
}

CloudBase (cloudfunctions/hello/index.js):

// cloudfunctions/hello/index.js
exports.main = async (event, context) => {
const { name = 'World' } = event.queryStringParameters || {}
return {
statusCode: 200,
body: JSON.stringify({ message: `Hello, ${name}!` }),
}
}

Tip: If you have more than 5 API routes, consider jumping to Scenario 2 and using CloudBase Run with Express for unified route management to avoid cloud function fragmentation.

Deployment Steps

# Step 1: Create cloud function directory
mkdir -p cloudfunctions/hello && cd cloudfunctions/hello

# Step 2: Initialize package.json (required for cloud functions)
npm init -y

# Step 3: Write the CloudBase version code above into index.js

# Step 4: Deploy
tcb fn deploy hello -e <envId>

# Step 5: Test invocation
tcb fn invoke hello -e <envId> --params '{"path":"/api/hello","httpMethod":"GET","queryStringParameters":{"name":"CloudBase"}}'

After deployment, check runtime status and logs at Cloud Functions → hello in the console.


2. Scenario 2: Multi-API Consolidation → CloudBase Run Express

When you have 5+ routes under api/, splitting into separate cloud functions is expensive to manage. Use a single CloudBase Run service to host all APIs.

// cloudrun-app/index.js (Express example)
const express = require('express')
const app = express()

app.get('/api/hello', (req, res) => {
res.json({ message: `Hello, ${req.query.name || 'World'}!` })
})

app.get('/api/users/:id', (req, res) => {
res.json({ id: req.params.id })
})

app.post('/api/webhook', express.json(), (req, res) => {
console.log('webhook received:', req.body)
res.json({ ok: true })
})

app.listen(3000)

Deployment Steps

# Step 1: Prepare Dockerfile
# FROM node:20-alpine
# WORKDIR /app
# COPY package*.json ./
# RUN npm ci --only=production
# COPY . .
# EXPOSE 3000
# CMD ["node", "index.js"]

# Step 2: Deploy to CloudBase Run
tcb cloudrun deploy -e <envId> -s api --source ./cloudrun-app

# Step 3: Access the app
# https://<envId>-api.ap-shanghai.run.tcloudbase.com/api/hello

3. Scenario 3: SSR Frameworks → CloudBase Run

CloudBase Run: containerized deployment, supports any language/framework, long-lived connections (SSE/WebSocket), no timeout limit. Console: CloudBase Run.

Next.js SSR / ISR / API Routes

Applicable when: project depends on Next.js server-side capabilities (SSR, API Routes, Middleware, ISR)

Prepare Dockerfile:

# Dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY .next .next
COPY public public
COPY next.config.js .

EXPOSE 3000

CMD ["node_modules/.bin/next", "start"]

Build and deploy:

# Build Next.js production version
npx next build

# Deploy to CloudBase Run (container mode) using CLI
# Note: CLI only supports --source to specify code directory; CPU/memory/instance count must be configured in console service details
tcb cloudrun deploy \
-e <envId> \
-s nextjs-app \
--source .

API Routes handling:

Vercel's /api/* routes run as-is in CloudBase Run with no code changes needed:

// pages/api/hello.ts (no changes needed)
export default function handler(req, res) {
res.status(200).json({ message: 'Hello from CloudBase!' })
}

Deployment command summary:

The entire Next.js app is packaged as a Docker image and deployed to CloudBase Run. API Routes, SSR, and Middleware are all auto-hosted:

# Step 1: Build Next.js production version
npx next build

# Step 2: Prepare Dockerfile (refer to template above)
# Step 3: Deploy to CloudBase Run
tcb cloudrun deploy -e <envId> -s nextjs-app --source .

# Step 4: Access the app
# After deployment, check the access domain in console "CloudBase Run → nextjs-app"

Nuxt.js / SvelteKit / Astro SSR

Same pattern as Next.js: Dockerfile + CloudBase Run deployment.

# Step 1: Build production output (choose one per framework)
npx nuxi build # Nuxt
npm run build # SvelteKit
npm run build # Astro

# Step 2: Prepare Dockerfile (choose the corresponding CMD per framework)
# Nuxt: CMD ["node", ".output/server/index.mjs"]
# SvelteKit: CMD ["node", "build/index.js"]
# Astro: CMD ["node", "dist/server/entry.mjs"]

# Step 3: Deploy to CloudBase Run
tcb cloudrun deploy -e <envId> -s my-app --source .

# Step 4: Avoid cold starts
# Set MinNum to ≥ 1 in console "CloudBase Run → Service Configuration"

Module 3: Database

CloudBase offers three database types: PostgreSQL, MySQL (relational) + Document Database (NoSQL). Console: Database.

Vercel DatabaseTypeCloudBase EquivalentNotes
Marketplace · Neon (relational Postgres)IntegrationPostgreSQL Database / MySQL DatabasePrefer PG for direct ecosystem compatibility
Marketplace · Supabase (full Postgres stack)IntegrationPostgreSQL DatabaseIncludes Auth/Storage/Realtime; database-only migration possible
Vercel Edge Config (low-latency read-only config)First-partyDocument DatabaseOnly suitable for infrequently written small data (feature flags, etc.)

1. Relational Data Migration

Recommended: Marketplace · Neon (Postgres) → CloudBase PostgreSQL (same ecosystem, directly compatible)

Overall Flow

Local export backup.sql → Upload to Cloud Storage (get fileID) → Deploy Cloud Function → Trigger execution
    Step 1       Step 2           Step 3     Step 4

Complete the import via Cloud Function + executePGSql, suitable for automation / large file batching.

Deployment Steps

Step 1: Export data locally (run locally, requires connectivity to Marketplace · Neon)

pg_dump --no-owner --no-acl --inserts postgres://<vercel-conn-str> > backup.sql

Step 2: Upload to Cloud Storage

Console Cloud Storage → Upload → get fileID (e.g. cloud://env-xxx/backup.sql).

Step 3: Deploy cloud function and configure environment variables

The cloud function requires TENCENTCLOUD_SECRETID / TENCENTCLOUD_SECRETKEY (console Cloud Functions → Function Configuration → Environment Variables).

mkdir -p cloudfunctions/pg-migrate && cd cloudfunctions/pg-migrate
npm init -y
npm i @cloudbase/manager-node
# Write the "Cloud Function Implementation" code below into index.js

tcb fn deploy pg-migrate -e <envId>

Step 4: Trigger execution (note: params passes fileID, not SQL content)

tcb fn invoke pg-migrate -e <envId> \
--params '{"envId":"<envId>","fileID":"cloud://<envId>/backup.sql"}'

Cloud Function Implementation

// cloudfunctions/pg-migrate/index.js
const CloudBase = require('@cloudbase/manager-node')

exports.main = async (event) => {
const { envId, fileID } = event
const app = CloudBase.init({
secretId: process.env.TENCENTCLOUD_SECRETID,
secretKey: process.env.TENCENTCLOUD_SECRETKEY,
envId
})

// 1. Download backup.sql from Cloud Storage
const { FileContent } = await app.storage.downloadFile({ fileID })
const sqlText = Buffer.from(FileContent).toString('utf-8')

// 2. Split SQL: remove comment lines + split by ;
// Note: $$ ... $$ function bodies and COPY ... FROM stdin are not suitable for this splitting
const stmts = sqlText
.split(/;\s*(?:\n|$)/)
.map(s => s.replace(/^--.*$/gm, '').trim())
.filter(Boolean)

// 3. Execute statements one by one, don't stop on failure (log errors)
let succeeded = 0
const errors = []
for (const sql of stmts) {
try {
await app.database.executePGSql({ Sql: sql + ';' })
succeeded++
} catch (e) {
errors.push({ sql: sql.slice(0, 200), message: e.message })
}
}
return { total: stmts.length, succeeded, failed: errors.length, errors }
}

Notes

  • executePGSql returns Columns + Rows + AffectedRows, supports DDL / DML / DQL.
  • ⚠️ Limitation: When pg_dump output includes CREATE FUNCTION ... $$ ... $$; or COPY ... FROM stdin;, the ;-based SQL splitting logic may break. Consider using --inserts mode to export pure INSERT statements, or add $$ block recognition to the script.

Module 4: File Migration

CloudBase Cloud Storage: built-in CDN domain, free HTTPS, image processing (thumbnails/crop/watermark), fine-grained security rules. Console: Storage → Cloud Storage.

CloudBase capability mapping: Vercel Blob is object storage; CloudBase's equivalent is Cloud Storage — with built-in CDN domain, free HTTPS, image processing (thumbnails/crop/watermark), and fine-grained security rules. The migration script for Blob files is provided below; code changes for existing upload/read operations mainly involve API replacement.

1. API Mapping

Vercel Blob (@vercel/blob)CloudBase Cloud Storage (@cloudbase/js-sdk v3 / @cloudbase/manager-node)Notes
put(path, file)app.uploadFile({ cloudPath, filePath })Direct path mapping (Vercel pathname → CloudBase cloudPath)
del(url)app.deleteFile({ fileList: [fileID] })CloudBase accepts cloud://... format fileID
get(path)app.getTempFileURL({ fileList: [{fileID, maxAge}] })Public read can get permanent links; private read must use temporary links
list({ prefix })Not in Web/Node SDK; available in Manager Node SDK as storage.listDirectoryFiles(cloudPath[, limit, marker])On browser, maintain file list in your database; in Node.js scripts or Cloud Functions, use @cloudbase/manager-node; or manually export from console Storage → File Management
head(path)No native batch API; Manager Node SDK provides storage.getFileInfo(cloudPath) for single file metadataFor batch scenarios, maintain inventory in Document Database; for single file queries, use @cloudbase/manager-node
access: 'public' / 'private'Storage permissions (console "Cloud Storage → Permission Settings")CloudBase uses directory-level permissions: public read / private read / various user identity reads

2. Migration Script (Node.js)

Suitable for small to medium volumes (tens of thousands of files). Vercel Blob lists all pathnames via list, downloads each one, then uploads to CloudBase. Since CloudBase SDK does not provide listFiles, the flow is: Vercel list → CloudBase write.

// install: npm i @vercel/blob @cloudbase/js-sdk
const { list } = require('@vercel/blob')
const cloudbase = require('@cloudbase/js-sdk').default
const fs = require('fs')
const path = require('path')

const app = cloudbase.init({ env: 'your-env-id' })
const TMP_DIR = path.join(__dirname, '_blob_tmp')

async function migrateVercelBlob({ prefix = '', token }) {
let cursor
let total = 0
do {
const { blobs, hasMore, cursor: next } = await list({ prefix, cursor, token })
for (const blob of blobs) {
// 1. Download from Vercel Blob to local
const localPath = path.join(TMP_DIR, blob.pathname)
fs.mkdirSync(path.dirname(localPath), { recursive: true })
const res = await fetch(blob.url) // Vercel Blob URL is directly downloadable
const buf = Buffer.from(await res.arrayBuffer())
fs.writeFileSync(localPath, buf)

// 2. Upload to CloudBase Cloud Storage (cloudPath matches Vercel pathname)
const { fileID } = await app.uploadFile({
cloudPath: blob.pathname,
fileContent: fs.createReadStream(localPath),
})
console.log(`${blob.pathname} -> ${fileID}`)
total++
}
cursor = next
} while (cursor)
console.log(`Migration complete, ${total} files in total`)
}

migrateVercelBlob({ token: process.env.BLOB_READ_WRITE_TOKEN })

For large files (GB-level), it is recommended to first download a ZIP archive from Vercel console "Blob → Download All", then upload in batches to CloudBase console "Cloud Storage → Upload" to avoid script timeouts from prolonged execution.

3. Script Usage

This section describes three ways to run the §2 migration script. vercel-blob-migrate is both the directory name and the cloud function name for the §2 script (they share the same name); any reference to this name below refers to the same script. Choose any one of A / B / C based on data volume.

The migration script must run in an environment with access to both Vercel and CloudBase APIs. Running it inside CloudBase's own Cloud Functions / CloudBase Run is recommended — no maintenance, built-in logs, and no local bandwidth usage.

Suitable for tens of thousands of files. Cloud Functions have a 5-minute timeout limit; it is recommended to add "batch size + cursor continuation" logic to split large tasks into multiple invocations.

# 1. Create cloud function directory
mkdir -p cloudfunctions/vercel-blob-migrate && cd cloudfunctions/vercel-blob-migrate

# 2. Initialize and install dependencies
npm init -y
npm i @vercel/blob @cloudbase/js-sdk

# 3. Write the §2 script into index.js

# 4. Configure environment variables (console: Cloud Functions → Function Configuration)
# BLOB_READ_WRITE_TOKEN = <your-vercel-blob-token>

# 5. Deploy
tcb fn deploy vercel-blob-migrate -e <envId>

# 6. Trigger migration
tcb fn invoke vercel-blob-migrate -e <envId>

Method B: As CloudBase Run (large volume, hundred-thousand to million level)

No timeout limit. Wrap the script as an Express HTTP interface with paginated migration support:

// cloudrun-blob-migrate/index.js
const express = require('express')
const { list } = require('@vercel/blob')
const cloudbase = require('@cloudbase/js-sdk').default

const app = express()
const tcb = cloudbase.init({ env: process.env.CLOUDBASE_ENV_ID })

app.get('/migrate', async (req, res) => {
const { cursor: startCursor } = req.query
const { blobs, cursor: nextCursor } = await list({
cursor: startCursor || undefined,
token: process.env.BLOB_READ_WRITE_TOKEN,
})
for (const blob of blobs) {
const response = await fetch(blob.url)
const buf = Buffer.from(await response.arrayBuffer())
await tcb.uploadFile({ cloudPath: blob.pathname, fileContent: buf })
}
res.json({ migrated: blobs.length, nextCursor, done: !nextCursor })
})

app.listen(3000)
# Deploy
tcb cloudrun deploy -e <envId> -s blob-migrate --source ./cloudrun-blob-migrate

# Trigger (paginate: pass cursor for each call)
curl "https://<envId>-blob-migrate.ap-shanghai.run.tcloudbase.com/migrate"
curl "https://<envId>-blob-migrate.ap-shanghai.run.tcloudbase.com/migrate?cursor=<nextCursor>"

Method C: Local Execution (small batch, under 1,000 files)

# Install dependencies (current project)
npm i @vercel/blob @cloudbase/js-sdk

# Run
BLOB_READ_WRITE_TOKEN=<token> node migrate.js

4. Business Code Change Points

  1. File ID format: Change from Vercel URL to CloudBase cloud:// format

    // Before (Vercel Blob)
    https://xxx.public.blob.vercel-storage.com/path/to/file.png
    // After (CloudBase)
    cloud://env-id.656e-env-id-1234567890/path/to/file.png
  2. Private read access: Use getTempFileURL to get temporary HTTPS links

    const { fileList } = await app.getTempFileURL({
    fileList: [{ fileID: 'cloud://env-xxx/path/to/file.png', maxAge: 3600 }],
    })
    const url = fileList[0].tempFileURL
  3. Public read: Enable directory-level public read via cloud storage permission settings, then access using the permanent CDN domain

  4. Client-side direct upload: Use app.uploadFile() instead of @vercel/blob/client

    // Before (Vercel Blob client)
    import { upload } from '@vercel/blob/client'
    const blob = await upload('file.png', file, { access: 'public', handleUploadUrl: '/api/upload' })
    // After (CloudBase Web SDK)
    const { fileID } = await app.uploadFile({ cloudPath: 'file.png', filePath: file })

Module 5: Authentication

CloudBase has built-in authentication supporting five login sources: WeChat Open Platform, SMS, email/password, anonymous, and custom login tickets. Console: Identity Authentication.

Third-Party Auth Selection Guide for Vercel Ecosystem

SolutionPositioningBest For
NextAuth (Auth.js)Open-source library, officially maintained by Next.jsNext.js projects (preferred)
ClerkHosted SaaS, out-of-box UIWhen you need ready-made login UI
Auth0Enterprise-grade identity platform (Okta subsidiary)Enterprise compliance, SSO

All can be replaced with CloudBase built-in authentication after migration.

API Mapping

OperationBefore (Vercel ecosystem)After (CloudBase)
Get current useruseUser() / getServerSession()(await auth.getSession()).data.session.user
Email/password loginClerk/Auth0/NextAuth SDKauth.signInWithPassword({ email, password })
SMS verification code loginThird-party SMS gatewayauth.signInWithOtp({ phone })data.verifyOtp({ token }) (Shanghai region only)
Third-party OAuthClerk social login / Auth0 SSOCustom login ticket + auth.signInWithCustomTicket(() => Promise.resolve(ticket))
LogoutsignOut()auth.signOut()
Token / sessionJWT / session cookie(await auth.getSession()).data.session.access_token
User management (admin)Clerk/Auth0 dashboardCloudBase console / Node SDK auth.*

Login Method Code Examples

Browser-side using @cloudbase/js-sdk:

import cloudbase from '@cloudbase/js-sdk'

const app = cloudbase.init({ env: 'your-env-id' })
const auth = app.auth // v3: `auth` is a property, not a method

// Anonymous login (suitable for quick prototyping)
await auth.signInAnonymously()

// SMS verification code login (Shanghai region only)
const { data } = await auth.signInWithOtp({ phone: '+8613800138000' })
await data.verifyOtp({ token: '123456' })

// Email + password login
await auth.signInWithPassword({ email: 'user@example.com', password: 'password' })

// WeChat Open Platform H5 login
await auth.signInWithWechatOpenPlatform({ appid: 'wx00...', scope: 'snsapi_base', redirectUri: 'https://...' })

// Custom login ticket (bridge for third-party OAuth, see below)
// Note: v3 signInWithCustomTicket takes a function, not an object
await auth.signInWithCustomTicket(() => Promise.resolve(ticket))

Existing User Migration

CloudBase does not provide a "batch import" console. You need to write an import-users cloud function to import users one by one.

Workflow:

  1. Export users from Clerk/Auth0/NextAuth as JSON/CSV:
  2. In the cloud function, call auth.signUpWithEmailAndPassword() to write each user to CloudBase.
// cloudfunctions/import-users/index.js
const cloudbase = require('@cloudbase/js-sdk').default
// Inside Cloud Function, env auto-detected if not passed
const app = cloudbase.init({ env: process.env.CLOUDBASE_ENV_ID })

const users = require('./users.json') // Exported user data

exports.main = async () => {
const results = []
for (const user of users) {
try {
// @cloudbase/js-sdk v3 signUp method
await app.auth.signUpWithEmailAndPassword({
email: user.email,
password: '<temporary-password>',
})
results.push({ email: user.email, status: 'success' })
} catch (e) {
results.push({ email: user.email, status: 'failed', error: e.message })
}
}
return results
}

⚠️ Password hashes cannot be directly migrated; it is recommended to enforce a password reset on first login.

Third-Party OAuth Bridging

Bridge Google / GitHub / NextAuth or other third-party OAuth through the "custom login ticket" channel:

// cloudfunctions/create-ticket/index.js (server side)
const cloudbase = require('@cloudbase/js-sdk').default
const { SignJWT } = require('jose')

exports.main = async (event) => {
// 1. Verify third-party OAuth identity (e.g., verify Google token)
// const googleUser = await verifyGoogleToken(event.googleToken)

// 2. Issue CloudBase custom login ticket (HS256, 5 min expiry)
const app = cloudbase.init({ env: process.env.CLOUDBASE_ENV_ID })
const ticket = await app.auth().createTicket('google:12345', { refresh: 3600 * 1000 })

return { ticket }
}
// Browser side (v3: signInWithCustomTicket takes a function, not an object)
const ticketRes = await fetch('/api/create-ticket', { method: 'POST', body: JSON.stringify({ googleToken }) })
const { ticket } = await ticketRes.json()
await auth.signInWithCustomTicket(() => Promise.resolve(ticket))

Module 6: Environment Variables

DimensionVercelCloudBase
Configuration locationDashboard "Settings → Environment Variables" / vercel env CLIConsole "Environment → Environment Variables" / tcb env:set CLI
EnvironmentsProduction / Preview / Development (per environment)Per CloudBase environment
Code accessprocess.env.XXXprocess.env.XXX (identical, no changes needed)
Local devvercel env pulltcb env:list -e <envId>
Secrets encryptionDashboard encrypted storageConsole encrypted storage

process.env.XXX access in code requires no changes at all.


Migration Script Tools

Two CLI tools to help batch migrate Vercel project configuration:

ScriptPurposeUsage
migrate-vercel-config.jsParse vercel.json, output CloudBase equivalent configurationnode migrate-vercel-config.js [vercel.json] (default ./vercel.json)
batch-migrate-functions.jsBatch convert api/ Vercel Functions to Cloud Functions / CloudBase Runnode batch-migrate-functions.js ./api [output-dir] --target=function (Cloud Functions mode, default)
node batch-migrate-functions.js ./api [output-dir] --target=cloudrun (CloudBase Run Express mode)

End-to-End Migration Flow

# 0. Prerequisites: Install CLI and log in
npm install -g @cloudbase/cli
tcb login

# 1. Convert vercel.json configuration
node migrate-vercel-config.js

# 2. Batch generate cloud function skeleton (Cloud Functions mode)
node batch-migrate-functions.js ./api cloudfunctions --target=function

# 3. Deploy cloud functions one by one
for dir in cloudfunctions/*/; do
name=$(basename "$dir")
tcb fn deploy "$name" -e <envId>
done

# 4. Deploy static site
tcb app deploy -e <envId>

Script 1: migrate-vercel-config.js

#!/usr/bin/env node
// Parse vercel.json → output CloudBase equivalent configuration

const fs = require('fs')
const path = require('path')

const configPath = process.argv[2] || './vercel.json'

if (!fs.existsSync(configPath)) {
console.error(`${configPath} not found`)
process.exit(1)
}

const vercel = JSON.parse(fs.readFileSync(configPath, 'utf-8'))

// 1. Static site hosting config (rewrites / redirects / headers)
const hosting = { rewrites: [], redirects: [], headers: [] }

if (vercel.rewrites) {
for (const r of vercel.rewrites) {
hosting.rewrites.push({
source: r.source,
destination: r.destination,
})
}
}

if (vercel.redirects) {
for (const r of vercel.redirects) {
hosting.redirects.push({
source: r.source,
destination: r.destination,
statusCode: r.permanent ? 301 : r.statusCode || 302,
})
}
}

if (vercel.headers) {
for (const h of vercel.headers) {
hosting.headers.push({
source: h.source,
headers: h.headers,
})
}
}

// 2. Functions mapping
const functions = vercel.functions || {}
const buildOutput = vercel.builds || []

// 3. Cron jobs
const crons = (vercel.crons || []).map(c => ({
path: c.path,
schedule: c.schedule,
}))

const result = JSON.stringify(
{
cloudbase: {
staticHosting: hosting,
functions: Object.keys(functions).length ? functions : '(no functions config)',
crons: crons.length ? crons : '(no crons)',
builds: buildOutput.map(b => ({ use: b.use, src: b.src })),
},
},
null,
2
)

console.log(result)
fs.writeFileSync('cloudbase-config.json', result)
console.log('\n✓ Output written to cloudbase-config.json')

Script 2: batch-migrate-functions.js

#!/usr/bin/env node
// Batch convert Vercel Functions → CloudBase Cloud Functions / CloudBase Run

const fs = require('fs')
const path = require('path')

const apiDir = process.argv[2] || './api'
const outputDir = process.argv[3] || './cloudfunctions'
const target = process.argv.find(a => a.startsWith('--target='))?.split('=')[1] || 'function'

if (!fs.existsSync(apiDir)) {
console.error(`${apiDir} not found`)
process.exit(1)
}

if (target === 'cloudrun') {
// CloudBase Run mode: generate an Express entry
const code = generateCloudRunEntry(apiDir)
fs.mkdirSync(outputDir, { recursive: true })
fs.writeFileSync(path.join(outputDir, 'index.js'), code)
fs.writeFileSync(
path.join(outputDir, 'package.json'),
JSON.stringify({ name: 'cloudrun-api', dependencies: { express: '^4.18.2' } }, null, 2)
)
console.log(`✓ CloudBase Run entry generated at ${outputDir}/index.js`)
process.exit(0)
}

// Cloud Functions mode: one function per file
fs.mkdirSync(outputDir, { recursive: true })

const files = fs.readdirSync(apiDir, { recursive: true }).filter(f => /\.[jt]s$/.test(f))

for (const file of files) {
const name = path.basename(file, path.extname(file)).replace(/^\[([^\]]+)\]$/, '_$1_')
const funcDir = path.join(outputDir, name)
fs.mkdirSync(funcDir, { recursive: true })

// Generate wrapper code
const wrapper = generateCloudFunctionWrapper(file)
fs.writeFileSync(path.join(funcDir, 'index.js'), wrapper)

fs.writeFileSync(
path.join(funcDir, 'package.json'),
JSON.stringify({ name: `fn-${name}`, main: 'index.js' }, null, 2)
)

console.log(`✓ Generated cloud function: ${name} (from ${file})`)
}

console.log(`\n✓ ${files.length} cloud function skeletons generated in ${outputDir}/`)
console.log(` Next: cd ${outputDir} && for d in */; do tcb fn deploy "$d" -e <envId>; done`)

// Helper function: generate cloud function wrapper
function generateCloudFunctionWrapper(filePath) {
const ext = path.extname(filePath)
const isTS = ext === '.ts'
const modulePath = path.join('../..', 'api', filePath.replace(ext, ''))

if (isTS) {
return `// CloudBase cloud function wrapper (TypeScript)
// Compile first: npx tsc || npx esbuild this-file --bundle --platform=node --outfile=index.cjs
// Or simply copy the handler logic directly into this file

exports.main = async (event, context) => {
const { default: handler } = await import('${modulePath}${ext}')
const response = await handler({
url: event.path,
method: event.httpMethod,
query: event.queryStringParameters || {},
body: event.body ? JSON.parse(event.body) : undefined,
headers: event.headers || {},
})
return {
statusCode: response.status || 200,
headers: response.headers || {},
body: JSON.stringify(response.body || response),
}
}
`
}

// JavaScript version
return `// CloudBase cloud function wrapper
// Automatically generated from: api/${filePath}

const handler = require('${modulePath}')

exports.main = async (event, context) => {
// Adapt event to Vercel handler signature
const req = {
url: event.path,
method: event.httpMethod,
query: event.queryStringParameters || {},
body: event.body ? JSON.parse(event.body) : undefined,
headers: event.headers || {},
}

const response = await handler(req)

return {
statusCode: response.statusCode || 200,
headers: { 'content-type': 'application/json', ...(response.headers || {}) },
body: JSON.stringify(response.body || response),
}
}
`
}

// Helper function: generate CloudBase Run entry (Express)
function generateCloudRunEntry(apiDir) {
const files = fs.readdirSync(apiDir, { recursive: true }).filter(f => /\.[jt]s$/.test(f))

let code = `// Auto-generated CloudBase Run Express entry (from Vercel Functions)
const express = require('express')
const app = express()
app.use(express.json())

`

for (const file of files) {
const route = '/' + file.replace(/\.(j|t)s$/, '').replace(/\[([^\]]+)\]/g, ':$1')
const modulePath = path.join('../..', 'api', file.replace(/\.(j|t)s$/, ''))
code += `app.all('${route}', async (req, res) => {
try {
const handler = require('${modulePath}')
const result = await handler(req)
res.status(result.statusCode || 200).json(result.body || result)
} catch (e) {
console.error('${route}:', e)
res.status(500).json({ error: e.message })
}
})

`
}

code += `
module.exports = app

// Direct run (for development use only; production uses start-server.js)
if (require.main === module) {
app.listen(3000, () => console.log('API server running on port 3000'))
}
`
return code
}

FAQ

1. SPA History Route 404

Set the static hosting error page to index.html:

tcb hosting config set -e <envId> --error-document index.html

Or configure in console: Static Website Hosting → Settings → Error Document → index.html.

2. Preview Deployments Alternative

Vercel Preview Deployments per Git branch can be replaced with multiple CloudBase environments (dev / staging / prod):

# Create environments
tcb env create dev # Development
tcb env create stage # Staging

# Deploy to respective environments
tcb app deploy -e dev
tcb app deploy -e stage
tcb app deploy -e prod

Add tcb app deploy -e <envId> to your CI/CD pipeline (GitHub Actions / GitLab CI) for the corresponding branch.

3. Timeout and Memory Comparison

Vercel FunctionsCloudBase Cloud FunctionsCloudBase Run
Timeout10s (Hobby) / 60s (Pro) / 900s (Enterprise)Up to 900sNo timeout limit
Memory128MB - 3008MB256MB - 3072MBConfigurable per container spec
Concurrency1 per invocation1 per instanceMultiple per container

4. Performance

  • Domestic (China): CloudBase uses Tencent Cloud CDN for global acceleration; domestic access speed is significantly better than Vercel (which has no mainland China edge nodes).
  • International: Both CloudBase and Vercel support CDN acceleration, with comparable performance.
  • Cold starts: CloudBase Cloud Functions can be configured with minNum >= 1 to keep instances warm, eliminating cold starts for production applications.