

在现代 DevOps 和大前端技术演进的浪潮中,Serverless(无服务器架构)已经从一种前沿概念沉淀为构建高可用、高扩展性 Web 系统的主流范式。对于具备大数据技术背景或追求极致性能的开发者而言,将基础设施的运维工作下放给云服务商,从而将核心精力聚焦于业务逻辑、全栈开发与极致的 UI 设计,是提升研发效能的最优解。
本文将深度剖析当前主流的 Serverless 与 PaaS(平台即服务)/ BaaS(后端即服务)生态,并探讨如何通过模块化组合,构建从极简静态站点到复杂的现代化全栈应用。
无服务器(Serverless)架构已成为现代Web应用开发的主流范式,它通过抽象基础设施管理,使开发者能够专注于业务逻辑实现。本文系统性地分析了当前主流的无服务器平台,包括静态托管、后端服务、数据库及边缘计算解决方案,并提出了针对不同应用场景的最佳组合策略。
Vercel是专为前端框架优化的无服务器部署平台,原生支持Next.js(由Vercel团队开发)、React、Vue、Svelte等现代框架。其核心技术特性包括:
Netlify是先驱级的JAMstack平台,提供完整的静态站点部署解决方案:
Cloudflare Pages充分利用Cloudflare的全球边缘网络,提供超低延迟的静态托管:
GitHub Pages是GitHub原生的静态站点托管服务:
Supabase是开源的Firebase替代方案,基于PostgreSQL构建:
Google的Firebase是综合性移动和Web应用平台:
AWS Amplify是AWS的全栈开发平台:
https://aws.amazon.com/amplify
Heroku是历史悠久的PaaS平台(现属Salesforce):
git push heroku main即部署Render是现代化的Heroku替代方案:
Cloudflare Pages + Cloudflare Workers + Supabase技术特性:
适用场景:全球化SaaS、实时协作工具
Vercel + Supabase + GitHub技术特性:
适用场景:创业公司MVP、现代Web应用
GitHub Pages + Cloudflare Pages Functions + Supabase技术特性:
适用场景:个人项目、开源应用、学习项目
AWS Amplify + DynamoDB + CloudFront + Cognito技术特性:
适用场景:企业内部系统、合规要求高的应用
前端框架 | 推荐托管平台 | 后端方案 | 数据库选择 |
|---|---|---|---|
Next.js | Vercel (首选) / Netlify | Vercel Functions / Supabase Edge Functions | Supabase / Firebase |
Nuxt 3 | Netlify / Cloudflare Pages | Netlify Functions / Nitro Server | Supabase / MongoDB Atlas |
SvelteKit | Vercel / Netlify / Cloudflare | Adapter-auto / Workers | Supabase / Firestore |
Astro | Cloudflare Pages / Netlify | SSR Adapters | Supabase / Turso |
Gatsby | Netlify (首选) / Vercel | Gatsby Functions | Contentful / Sanity |
Remix | Vercel / Cloudflare Pages | Remix Server | Supabase / Prisma+PlanetScale |
Angular | Firebase Hosting / Netlify | Cloud Functions / Netlify Functions | Firestore / Supabase |
Vue 3 (Vite) | Netlify / Vercel | Nitro / Serverless Functions | Supabase / Firebase |
技术栈:
# 1. 项目初始化
npm create astro@latest my-blog
cd my-blog
# 2. 配置Cloudflare Pages适配器
npm install @astrojs/cloudflare
# astro.config.mjs
export default defineConfig({
output: 'hybrid',
adapter: cloudflare()
})
# 3. 推送到GitHub
git init && git add . && git commit -m "init"
git remote add origin <your-repo>
git push -u origin main
# 4. 在Cloudflare Pages连接仓库
# 构建命令:npm run build
# 输出目录:dist技术栈:
用户请求
→ Vercel Edge Network (全球CDN)
→ Next.js SSR/ISR (动态内容)
→ Vercel Serverless Functions (API Routes)
→ Supabase (数据 + 实时订阅)
→ Stripe (支付处理)1. 数据库Schema(Supabase)
-- 启用Row Level Security
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
owner_id UUID REFERENCES auth.users NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS策略
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own projects"
ON projects FOR SELECT
USING (auth.uid() = owner_id);2. 实时协作(Next.js + Supabase)
// app/project/[id]/page.tsx
'use client'
import { createClient } from '@/lib/supabase/client'
import { useEffect, useState } from 'react'
export default function ProjectPage({ params }) {
const [tasks, setTasks] = useState([])
const supabase = createClient()
useEffect(() => {
// 订阅实时更新
const channel = supabase
.channel('tasks')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'tasks' },
(payload) => {
setTasks(current => [...current, payload.new])
}
)
.subscribe()
return () => supabase.removeChannel(channel)
}, [])
return <TaskList tasks={tasks} />
}3. 支付集成(Vercel Serverless Function)
// app/api/webhook/stripe/route.ts
import { headers } from 'next/headers'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
export async function POST(req: Request) {
const body = await req.text()
const signature = headers().get('stripe-signature')!
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
)
if (event.type === 'checkout.session.completed') {
// 更新Supabase用户订阅状态
const session = event.data.object
await supabase
.from('subscriptions')
.insert({ user_id: session.client_reference_id })
}
return new Response(JSON.stringify({ received: true }))
}技术栈:
用户访问
→ Cloudflare CDN (静态资源)
→ Cloudflare Workers (地理位置定价、个性化推荐)
→ Remix SSR (Cloudflare Pages)
→ Shopify Storefront API (商品数据)
→ Supabase (用户愿望单、浏览历史)
→ Algolia (搜索)1. 边缘个性化定价
// functions/[[path]].ts (Cloudflare Worker)
export async function onRequest(context) {
const country = context.request.cf.country
// 根据地理位置调整货币
const currency = COUNTRY_CURRENCY_MAP[country] || 'USD'
// 注入到HTML
const response = await context.next()
return new HTMLRewriter()
.on('meta[name="currency"]', {
element(el) {
el.setAttribute('content', currency)
}
})
.transform(response)
}2. Shopify集成
// app/routes/products.$handle.tsx
import { useLoaderData } from '@remix-run/react'
import { getStorefrontApiUrl, getPrivateTokenHeaders } from '@shopify/hydrogen'
export async function loader({ params }) {
const response = await fetch(getStorefrontApiUrl(), {
method: 'POST',
headers: getPrivateTokenHeaders(),
body: JSON.stringify({
query: `
query ProductByHandle($handle: String!) {
productByHandle(handle: $handle) {
id
title
variants(first: 10) {
edges {
node { priceV2 { amount currencyCode } }
}
}
}
}
`,
variables: { handle: params.handle }
})
})
return response.json()
}技术栈:
客户端 (SvelteKit)
↕ WebSocket (Supabase Realtime)
↕ PostgreSQL (消息持久化)
→ Supabase Storage (文件/图片)
→ FCM (离线推送)1. 实时消息(Supabase Realtime)
// src/routes/chat/[roomId]/+page.svelte
<script lang="ts">
import { onMount } from 'svelte'
import { supabase } from '$lib/supabase'
export let data
let messages = data.messages
let newMessage = ''
onMount(() => {
// 订阅新消息
const subscription = supabase
.channel(`room:${data.roomId}`)
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: `room_id=eq.${data.roomId}`
}, (payload) => {
messages = [...messages, payload.new]
})
.subscribe()
return () => subscription.unsubscribe()
})
async function sendMessage() {
await supabase.from('messages').insert({
room_id: data.roomId,
content: newMessage,
user_id: data.user.id
})
newMessage = ''
}
</script>2. 在线状态追踪(Presence)
// src/lib/stores/presence.ts
import { supabase } from '$lib/supabase'
import { writable } from 'svelte/store'
export const onlineUsers = writable([])
export function trackPresence(roomId: string, user: User) {
const channel = supabase.channel(`presence:${roomId}`, {
config: { presence: { key: user.id } }
})
channel
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
onlineUsers.set(Object.values(state).flat())
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({
user_id: user.id,
online_at: new Date().toISOString()
})
}
})
return () => channel.unsubscribe()
}3. 文件上传(Supabase Storage)
// src/routes/api/upload/+server.ts
import { supabase } from '$lib/supabase/server'
export async function POST({ request }) {
const formData = await request.formData()
const file = formData.get('file') as File
const { data, error } = await supabase.storage
.from('chat-media')
.upload(`${crypto.randomUUID()}-${file.name}`, file, {
cacheControl: '3600',
upsert: false
})
if (error) throw error
const { data: { publicUrl } } = supabase.storage
.from('chat-media')
.getPublicUrl(data.path)
return new Response(JSON.stringify({ url: publicUrl }))
}range()实现虚拟滚动last_read_at字段跟踪技术栈:
Markdown文件 (GitHub Repo)
→ GitHub Actions (自动构建)
→ Vercel (部署)
→ Algolia Crawler (索引更新)
→ 用户访问 (搜索 + 阅读)1. Nextra配置
// next.config.js
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.tsx',
latex: true,
defaultShowCopyCode: true
})
module.exports = withNextra({
i18n: {
locales: ['en', 'zh', 'ja'],
defaultLocale: 'en'
},
redirects: async () => [
{
source: '/docs',
destination: '/docs/getting-started',
permanent: true
}
]
})2. Algolia爬虫配置
{
"index_name": "my-docs",
"start_urls": ["https://docs.example.com"],
"selectors": {
"lvl0": ".nextra-breadcrumb",
"lvl1": "article h1",
"lvl2": "article h2",
"lvl3": "article h3",
"text": "article p, article li"
}
}3. GitHub Actions自动部署
# .github/workflows/algolia-reindex.yml
name: Reindex Algolia
on:
push:
branches: [main]
paths: ['pages/**']
jobs:
reindex:
runs-on: ubuntu-latest
steps:
- name: Algolia Crawler
uses: algolia/algoliasearch-crawler-github-actions@v1
with:
crawler-user-id: ${{ secrets.CRAWLER_USER_ID }}
crawler-api-key: ${{ secrets.CRAWLER_API_KEY }}
algolia-app-id: ${{ secrets.ALGOLIA_APP_ID }}
algolia-api-key: ${{ secrets.ALGOLIA_API_KEY }}
site-url: 'https://docs.example.com'技术栈:
客户端请求
→ Cloudflare Workers (边缘API)
→ 速率限制检查 (KV)
→ 缓存查询 (KV)
→ Supabase (数据源)
→ 响应 (边缘缓存)1. Workers API实现(Hono)
// src/index.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { cache } from 'hono/cache'
import { rateLimiter } from './middleware/rateLimiter'
const app = new Hono()
app.use('*', cors())
app.use('*', rateLimiter({ limit: 100, window: 60 }))
app.get('/api/users/:id',
cache({
cacheName: 'users',
cacheControl: 'max-age=300'
}),
async (c) => {
const { id } = c.req.param()
const { KV, SUPABASE } = c.env
// 检查KV缓存
const cached = await KV.get(`user:${id}`)
if (cached) return c.json(JSON.parse(cached))
// 查询Supabase
const user = await fetch(`${SUPABASE}/rest/v1/users?id=eq.${id}`, {
headers: {
'apikey': c.env.SUPABASE_KEY,
'Authorization': `Bearer ${c.env.SUPABASE_KEY}`
}
}).then(r => r.json())
// 写入缓存(5分钟)
await KV.put(`user:${id}`, JSON.stringify(user), { expirationTtl: 300 })
return c.json(user)
}
)
export default app2. 速率限制中间件
// src/middleware/rateLimiter.ts
import { Context, Next } from 'hono'
interface RateLimitOptions {
limit: number
window: number // 秒
}
export function rateLimiter(options: RateLimitOptions) {
return async (c: Context, next: Next) => {
const ip = c.req.header('CF-Connecting-IP') || 'unknown'
const key = `ratelimit:${ip}`
const { KV } = c.env
const current = await KV.get(key)
const count = current ? parseInt(current) : 0
if (count >= options.limit) {
return c.json({ error: 'Rate limit exceeded' }, 429)
}
await KV.put(key, String(count + 1), { expirationTtl: options.window })
await next()
}
}3. OpenAPI文档生成
// src/openapi.ts
import { OpenAPIHono } from '@hono/zod-openapi'
import { z } from 'zod'
const app = new OpenAPIHono()
app.openapi(
{
method: 'get',
path: '/api/users/{id}',
request: {
params: z.object({ id: z.string().uuid() })
},
responses: {
200: {
description: 'User found',
content: {
'application/json': {
schema: z.object({
id: z.string(),
email: z.string().email(),
created_at: z.string()
})
}
}
}
}
},
async (c) => {
// 处理逻辑
}
)
// 暴露文档
app.doc('/openapi.json', {
openapi: '3.1.0',
info: { title: 'My API', version: '1.0.0' }
})方案设计:
北美用户 → Vercel (us-east-1) → Supabase (us-east-1)
欧洲用户 → Vercel (eu-west-1) → Supabase (eu-west-1)
亚太用户 → Vercel (ap-southeast-1) → Supabase (ap-southeast-1)关键技术:
成本优化:
技术栈:
架构流程:
用户下单
→ Supabase插入订单记录
→ Database Webhook触发
→ Cloudflare Worker处理事件
├→ 发送确认邮件 (Resend)
├→ 通知仓库系统 (Queue)
└→ 记录分析事件 (Tinybird)实现代码:
// Supabase Webhook配置
CREATE TRIGGER on_order_created
AFTER INSERT ON orders
FOR EACH ROW
EXECUTE FUNCTION supabase_functions.http_request(
'https://api.example.com/webhooks/order',
'POST',
'{"Content-Type":"application/json"}',
'{}',
'1000'
);
// Cloudflare Worker处理
export default {
async fetch(request, env) {
const { record } = await request.json()
// 发送到队列
await env.ORDER_QUEUE.send({
orderId: record.id,
timestamp: Date.now()
})
// 发送邮件
await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${env.RESEND_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: 'orders@example.com',
to: record.customer_email,
subject: 'Order Confirmation',
html: `<p>Order #${record.id} confirmed</p>`
})
})
return new Response('OK')
}
}技术栈:
架构设计:
Shell App (example.com)
├─ /dashboard → React App (dashboard.example.com)
├─ /analytics → Vue App (analytics.example.com)
└─ /settings → Svelte App (settings.example.com)Shell应用配置:
// next.config.js
const { withModuleFederation } = require('@module-federation/nextjs-mf')
module.exports = withModuleFederation({
name: 'shell',
remotes: {
dashboard: `dashboard@https://dashboard.example.com/_next/static/chunks/remoteEntry.js`,
analytics: `analytics@https://analytics.example.com/remoteEntry.js`,
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
})服务 | 免费额度 | 用途 | 成本 |
|---|---|---|---|
Cloudflare Pages | 无限带宽 | 静态托管 | $0 |
Cloudflare Workers | 100k请求/天 | API逻辑 | $0 |
Supabase | 500MB数据库 + 1GB存储 | 数据持久化 | $0 |
Cloudflare R2 | 10GB存储 | 文件存储 | $0 |
Resend | 3k邮件/月 | 事务邮件 | $0 |
自定义域名 | - | - | $12/年 |
总成本:$1/月(域名分摊)
适用流量:月活<10k用户
工具组合:
告警脚本(Cloudflare Worker Cron):
// 每日成本检查
export default {
async scheduled(event, env, ctx) {
// 查询Vercel API
const vercelUsage = await fetch('https://api.vercel.com/v1/usage', {
headers: { Authorization: `Bearer ${env.VERCEL_TOKEN}` }
}).then(r => r.json())
// 查询Supabase
const supabaseUsage = await fetch(`${env.SUPABASE_URL}/rest/v1/rpc/get_usage`, {
headers: { apikey: env.SUPABASE_KEY }
}).then(r => r.json())
const totalCost = vercelUsage.cost + (supabaseUsage.db_size > 500 ? 25 : 0)
if (totalCost > 80) {
await sendAlert(`⚠️ 本月成本已达 $${totalCost}`)
}
}
}Cloudflare Images方案:
// next.config.js
module.exports = {
images: {
loader: 'custom',
loaderFile: './lib/cloudflareImageLoader.ts'
}
}
// lib/cloudflareImageLoader.ts
export default function cloudflareImageLoader({ src, width, quality }) {
const params = [`width=${width}`]
if (quality) params.push(`quality=${quality}`)
return `https://example.com/cdn-cgi/image/${params.join(',')}/${src}`
}效果:
Next.js动态导入:
// 组件级代码分割
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <Skeleton />,
ssr: false // 仅客户端加载
})
// 路由级分割(自动)
// app/dashboard/page.tsx → 独立chunkWebpack Bundle分析:
npm install -D @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
# 运行分析
ANALYZE=true npm run build-- 分析慢查询
SELECT
query,
mean_exec_time,
calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
-- 创建复合索引
CREATE INDEX idx_posts_user_created
ON posts(user_id, created_at DESC);
-- 部分索引(节省空间)
CREATE INDEX idx_active_users
ON users(last_seen)
WHERE status = 'active';
-- JSONB索引
CREATE INDEX idx_metadata_gin
ON products USING GIN (metadata);使用Prepared Statements:
// ❌ 不推荐
const users = await supabase
.from('users')
.select('*')
.ilike('name', `%${search}%`)
// ✅ 推荐
const users = await supabase.rpc('search_users', { search_term: search })
-- SQL函数(启用缓存)
CREATE OR REPLACE FUNCTION search_users(search_term TEXT)
RETURNS TABLE(...) AS $$
SELECT * FROM users WHERE name ILIKE '%' || search_term || '%'
$$ LANGUAGE sql STABLE;// Cloudflare Worker
export default {
async fetch(request, env, ctx) {
const cache = caches.default
// 检查缓存
let response = await cache.match(request)
if (!response) {
response = await fetch(request)
// 缓存成功响应(1小时)
if (response.ok) {
response = new Response(response.body, response)
response.headers.set('Cache-Control', 'public, max-age=3600')
ctx.waitUntil(cache.put(request, response.clone()))
}
}
return response
}
}// app/api/data/route.ts
export const runtime = 'edge'
export async function GET() {
const data = await fetchData()
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, s-maxage=600, stale-while-revalidate=1200'
}
})
}// 启用多因素认证
await supabase.auth.mfa.enroll({
factorType: 'totp',
friendlyName: 'My App'
})
// OAuth配置(GitHub)
await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
scopes: 'read:user user:email',
redirectTo: `${location.origin}/auth/callback`
}
})
// Row Level Security
CREATE POLICY "Users can only see own data"
ON private_data FOR SELECT
USING (auth.uid() = user_id);Vercel项目配置:
# 生产环境
vercel env add DATABASE_URL production
# 预览环境
vercel env add DATABASE_URL preview
# 本地开发
vercel env pull .env.local加密存储(Cloudflare Workers):
// 使用Web Crypto API
async function encrypt(data: string, key: CryptoKey): Promise<string> {
const encoded = new TextEncoder().encode(data)
const iv = crypto.getRandomValues(new Uint8Array(12))
const cipher = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encoded
)
return btoa(String.fromCharCode(...new Uint8Array(cipher)))
}// Worker速率限制
const RATE_LIMIT = 100 // 每分钟请求数
export default {
async fetch(request, env) {
const ip = request.headers.get('CF-Connecting-IP')
const key = `rate:${ip}`
const count = await env.KV.get(key)
if (count && parseInt(count) > RATE_LIMIT) {
return new Response('Too Many Requests', {
status: 429,
headers: { 'Retry-After': '60' }
})
}
await env.KV.put(key, String((parseInt(count || '0') + 1)), {
expirationTtl: 60
})
return fetch(request)
}
}// next.config.js
const { withSentryConfig } = require('@sentry/nextjs')
module.exports = withSentryConfig(
{ /* Next.js配置 */ },
{
org: 'my-org',
project: 'my-project',
silent: true,
widenClientFileUpload: true,
tunnelRoute: '/monitoring'
}
)
// app/error.tsx
'use client'
import * as Sentry from '@sentry/nextjs'
import { useEffect } from 'react'
export default function Error({ error }) {
useEffect(() => {
Sentry.captureException(error)
}, [error])
return <div>Something went wrong!</div>
}// app/layout.tsx
import { Analytics } from '@vercel/analytics/react'
import { SpeedInsights } from '@vercel/speed-insights/next'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
)
}// lib/analytics.ts
import { sendToVercelAnalytics } from '@vercel/analytics'
export function trackEvent(name: string, data: Record<string, any>) {
sendToVercelAnalytics({
name,
data,
timestamp: Date.now()
})
}
// 使用
trackEvent('checkout_completed', {
order_id: '123',
amount: 99.99,
currency: 'USD'
})# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run test
- run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# 部署到Vercel
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
# 数据库迁移
- name: Run Migrations
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run migrate
# 通知Sentry发布
- name: Create Sentry Release
uses: getsentry/action-release@v1
with:
environment: production
version: ${{ github.sha }}# .github/workflows/preview.yml
name: Preview Deployment
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy Preview
id: deploy
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
github-comment: true
- name: Run E2E Tests
env:
PREVIEW_URL: ${{ steps.deploy.outputs.preview-url }}
run: |
npm run test:e2e -- --baseUrl=$PREVIEW_URL项目类型?
├─ 静态网站(博客/文档)
│ → GitHub Pages / Cloudflare Pages
│
├─ JAMstack应用
│ ├─ Next.js → Vercel
│ ├─ Astro/SvelteKit → Netlify/Cloudflare Pages
│ └─ Nuxt → Netlify
│
├─ 全栈应用
│ ├─ 无数据库 → Vercel + KV
│ ├─ 需SQL → Vercel + Supabase
│ └─ 需NoSQL → Vercel + Firebase
│
├─ API服务
│ ├─ 低延迟 → Cloudflare Workers
│ ├─ 复杂逻辑 → Render/AWS Lambda
│ └─ 长运行任务 → Render Background Workers
│
└─ 实时应用
├─ 聊天/协作 → Supabase Realtime
├─ 游戏 → Cloudflare Durable Objects
└─ 直播 → Cloudflare Stream无服务器架构已成为现代Web开发的标准范式,合理的平台组合能够在保证性能和可靠性的同时,显著降低运维复杂度和成本。本文提出的组合策略和最佳实践已在多个生产环境中验证,开发者应根据具体场景灵活选择,避免过度工程化。
核心建议:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。