Vercel & Netlify 部署
大白话解释: Vercel 和 Netlify 就像"前端项目的自动部署平台"。你把代码推到 GitHub,它们自动帮你构建、部署、配置 HTTPS、生成预览链接,全程不用手动操作。
为什么要用 Vercel/Netlify?
- 零配置部署:不用自己买服务器、不用配置 Nginx、不用手动部署
- 自动 CI/CD:推代码就自动部署,不用写 GitHub Actions
- 预览部署:每个 PR 自动生成预览链接,方便测试
- 免费额度:个人项目完全够用
Vercel vs Netlify 怎么选?
- Vercel:Next.js 项目首选、Serverless Functions 更强、国内访问稍快
- Netlify:静态站点更成熟、表单处理更方便、社区插件更多
什么时候用 Vercel/Netlify?
- 个人项目、博客、文档站
- 需要快速部署原型
- 不想管理服务器
- 需要预览部署功能
Vercel 和 Netlify 是前端最流行的静态站点部署平台,支持自动构建、预览部署、自定义域名、HTTPS 等功能。
Vercel
特点
- Next.js 官方平台(Vercel 开发)
- 零配置部署 Vue/React/Vite 项目
- 自动 HTTPS、CDN 加速
- 预览部署(PR 自动生成预览链接)
- Serverless Functions 支持
部署方式
方式一:Git 集成(推荐)
- 登录 vercel.com
- 点击 "New Project"
- 导入 GitHub/GitLab 仓库
- 自动检测框架,点击 Deploy
方式二:CLI 部署
# 安装 CLI
npm i -g vercel
# 登录
vercel login
# 部署(首次会交互式配置)
vercel
# 部署到生产环境
vercel --prodVercel CLI 常用命令
# 环境变量管理
vercel env add <name> # 添加环境变量(交互式选择环境)
vercel env add <name> production # 仅添加到生产环境
vercel env ls # 列出所有环境变量
vercel env rm <name> # 删除环境变量
vercel env pull .env.local # 拉取环境变量到本地文件
# 域名管理
vercel domains add <domain> # 添加自定义域名
vercel domains ls # 列出已绑定域名
vercel domains rm <domain> # 移除域名
# 日志与调试
vercel logs <url> # 查看函数运行日志
vercel logs --follow # 实时跟踪日志
vercel inspect <url> # 查看部署详情(大小、函数、路由等)
# 部署管理
vercel ls # 列出项目部署记录
vercel promote <url> # 将指定部署提升为生产环境
vercel rollback # 回滚到上一次生产部署
vercel rm <url> # 删除指定部署
# 项目配置
vercel link # 关联本地项目与 Vercel 项目
vercel env pull # 同步环境变量
vercel build # 本地模拟 Vercel 构建vercel.json 配置
{
"buildCommand": "yarn build",
"outputDirectory": "dist",
"framework": "vite"
}Rewrites(重写规则)
重写不会改变 URL,仅将请求内部代理到目标路径,适合 SPA 路由和 API 代理。
{
"rewrites": [
{ "source": "/old-path", "destination": "/new-path" },
{ "source": "/api/(.*)", "destination": "https://api.example.com/$1" },
{ "source": "/(.*)", "destination": "/index.html" }
]
}| 字段 | 说明 |
|---|---|
source | 匹配路径(支持正则 (.*) 和命名捕获 :path) |
destination | 目标路径(可用 $1、:path 引用捕获组) |
has | 条件匹配(header、cookie、query) |
missing | 缺失条件时匹配 |
{
"rewrites": [
{
"source": "/blog/:slug",
"has": [{ "type": "header", "key": "x-rewrite", "value": "true" }],
"destination": "/blog-rewrite/:slug"
},
{
"source": "/:path((?!api/).*)",
"destination": "/index.html"
}
]
}Redirects(重定向规则)
重定向会改变浏览器 URL,返回 301/302 状态码。
{
"redirects": [
{ "source": "/old-blog/:slug", "destination": "/blog/:slug", "permanent": true },
{ "source": "/docs", "destination": "/docs/intro", "statusCode": 302 },
{ "source": "/github", "destination": "https://github.com/user/repo" },
{
"source": "/api/:path*",
"destination": "https://backend.example.com/:path*",
"permanent": false,
"has": [{ "type": "header", "key": "host", "value": "api.example.com" }]
}
]
}| 字段 | 说明 |
|---|---|
permanent | true 为 301 永久重定向,false 为 302 临时重定向 |
statusCode | 自定义状态码(301/302/307/308) |
has | 仅在满足条件时触发重定向 |
Headers 配置
{
"headers": [
{
"source": "/assets/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
]
},
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
},
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" },
{ "key": "Access-Control-Allow-Methods", "value": "GET, POST, PUT, DELETE, OPTIONS" },
{ "key": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" }
]
}
]
}| 常用 Header | 用途 |
|---|---|
Cache-Control | 静态资源长期缓存,HTML 不缓存 |
X-Frame-Options | 防止点击劫持(DENY / SAMEORIGIN) |
X-Content-Type-Options | 防止 MIME 嗅探(nosniff) |
Content-Security-Policy | 防止 XSS 攻击 |
Strict-Transport-Security | 强制 HTTPS(HSTS) |
环境变量
# 在 Vercel 控制台设置
# Settings → Environment Variables
# 或通过 CLI
vercel env add VITE_API_URL
# 拉取环境变量到本地
vercel env pull .env.local预览部署
- 每次 Push 到非 main 分支,自动生成预览 URL
- PR 中会显示预览链接,方便代码审查
- 可以在控制台关闭预览部署
Serverless Functions
Vercel 原生支持 Serverless Functions,放置在 api/ 目录下即可自动部署。
Node.js Functions
// api/hello.js — 最简单的 Serverless Function
// 放在 api/ 目录下,Vercel 自动识别为 API 端点
// 访问:GET /api/hello?name=Alice → { message: "Hello, Alice!" }
export default function handler(req, res) {
const { name = 'World' } = req.query; // 从 URL 查询参数获取 name,默认 "World"
res.status(200).json({ message: `Hello, ${name}!` }); // 返回 JSON 响应
}// api/user/[id].js — 动态路由(方括号语法)
// 访问:GET /api/user/123 → { userId: "123" }
// [id] 是动态参数,匹配 /api/user/xxx 的任意路径
export default function handler(req, res) {
const { id } = req.query; // 获取 URL 中的动态参数 id
res.status(200).json({ userId: id }); // 返回用户 ID
}Python Functions
# api/hello.py — Python Serverless Function
# Vercel 自动检测 .py 文件,使用 Python 运行时执行
# 访问:GET /api/hello?name=Alice → { "message": "Hello, Alice!" }
def handler(request):
name = request.args.get("name", "World") # 从查询参数获取 name,默认 "World"
return {"message": f"Hello, {name}!"} # 返回字典,自动序列化为 JSONGo Functions
// api/hello.go — Go Serverless Function
package handler
import (
"fmt"
"net/http"
)
func Handler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name") // 从查询参数获取 name
if name == "" {
name = "World" // 默认值
}
// 写入 JSON 格式的响应
fmt.Fprintf(w, `{"message":"Hello, %s!"}`, name)
}Functions 配置
// api/data.js — 自定义 Function 配置
// 通过 export config 覆盖默认行为
export const config = {
runtime: 'nodejs20', // 指定运行时版本(可选:nodejs18/20/22)
maxDuration: 10, // 最大执行时间(秒),免费版上限 10s
regions: ['iad1', 'sfo1'], // 部署区域(减少延迟)
};| 运行时 | 支持版本 | 冷启动时间 |
|---|---|---|
| Node.js | 18 / 20 / 22 | ~250ms |
| Python | 3.9 / 3.10 / 3.11 | ~400ms |
| Go | 1.21+ | ~100ms |
| Ruby | 3.2 / 3.3 | ~500ms |
Edge Functions
Edge Functions 运行在 CDN 边缘节点,延迟更低,适合做请求改写、A/B 测试、地理定向等。
// middleware.ts(项目根目录)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US';
if (country === 'CN') {
return NextResponse.redirect(new URL('/zh', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};// api/edge-hello.ts
export const config = { runtime: 'edge' };
export default function handler(request: Request) {
const url = new URL(request.url);
const name = url.searchParams.get('name') || 'World';
return new Response(JSON.stringify({ message: `Hello, ${name}!` }), {
headers: { 'Content-Type': 'application/json' },
});
}| 特性 | Serverless Functions | Edge Functions |
|---|---|---|
| 运行位置 | 区域服务器 | 全球边缘节点 |
| 冷启动 | 较慢(~250ms) | 极快(<50ms) |
| Node.js API | 完整 | 子集(Web API) |
| 最大执行时间 | 60s(Pro) | 30s |
| 适用场景 | 数据库查询、复杂逻辑 | 请求改写、认证、重定向 |
ISR / SSG / SSR 支持
Vercel 对 Next.js 的渲染模式提供全面支持:
| 模式 | 说明 | Vercel 支持 |
|---|---|---|
| SSG | 构建时生成静态页面 | 默认支持 |
| SSR | 每次请求服务端渲染 | 自动适配 |
| ISR | 增量静态再生成 | revalidate 参数控制 |
| PPR | 部分预渲染(实验性) | Next.js 15+ 支持 |
// ISR 示例:每 60 秒重新生成页面
export const revalidate = 60;
export default async function Page() {
const data = await fetch('https://api.example.com/posts');
const posts = await data.json();
return <PostList posts={posts} />;
}// 按需再生成(On-Demand Revalidation)
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(request: Request) {
const { path, tag } = await request.json();
if (path) revalidatePath(path);
if (tag) revalidateTag(tag);
return Response.json({ revalidated: true });
}Vercel Cron Jobs(定时任务)
Vercel Cron Jobs 可定时触发 Serverless Functions,适合数据同步、清理任务等。
// vercel.json
{
"crons": [
{ "path": "/api/cron/daily", "schedule": "0 0 * * *" },
{ "path": "/api/cron/hourly", "schedule": "0 * * * *" },
{ "path": "/api/cron/weekly", "schedule": "0 0 * * 1" }
]
}// app/api/cron/daily/route.ts
import { NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export async function GET(request: Request) {
const authHeader = request.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 执行定时任务逻辑
await syncDatabase();
await cleanupExpiredData();
return NextResponse.json({ success: true, executedAt: new Date().toISOString() });
}💡 在 Vercel 控制台 Settings → Environment Variables 中设置
CRON_SECRET,Cron 请求会自动携带Authorization: Bearer <CRON_SECRET>头。
| 计划 | Cron 表达式 | 说明 |
|---|---|---|
| 每天午夜 | 0 0 * * * | UTC 时间 |
| 每小时 | 0 * * * * | 整点执行 |
| 每周一 | 0 0 * * 1 | UTC 周一午夜 |
| 每月 1 号 | 0 0 1 * * | UTC 每月首日 |
💡 免费版最多 1 个 Cron Job,Pro 版最多 40 个。Cron 仅在生产环境生效。
Vercel Analytics
# 安装
npm install @vercel/analytics// app/layout.tsx 或 pages/_app.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>
);
}| 功能 | 免费版 | Pro 版 |
|---|---|---|
| 页面浏览量 | ✅ | ✅ |
| Web Vitals | ✅ | ✅ |
| 自定义事件 | 有限 | 完整 |
| 实时数据 | ❌ | ✅ |
| 数据保留 | 24 小时 | 12 个月 |
Vercel 存储服务
| 服务 | 用途 | 说明 |
|---|---|---|
| Vercel KV | 键值存储 | 基于 Upstash Redis,适合会话、缓存 |
| Vercel Postgres | 关系型数据库 | 基于 Neon,Serverless PostgreSQL |
| Vercel Blob | 文件存储 | 大文件上传、图片存储 |
// Vercel KV 示例
import { kv } from '@vercel/kv';
await kv.set('user:1', JSON.stringify({ name: 'Alice' }));
const user = await kv.get('user:1');
// Vercel Postgres 示例
import { sql } from '@vercel/postgres';
const { rows } = await sql`SELECT * FROM users WHERE id = ${1}`;
// Vercel Blob 示例
import { put, del } from '@vercel/blob';
const blob = await put('avatar.png', file, { access: 'public' });
await del(blob.url);Vercel 免费额度详细说明
| 资源 | Hobby(免费) | Pro | Enterprise |
|---|---|---|---|
| 带宽 | 100GB/月 | 1TB/月 | 自定义 |
| 构建时间 | 6,000 分钟/月 | 24,000 分钟/月 | 自定义 |
| Serverless Functions 执行 | 100GB-Hrs | 1000GB-Hrs | 自定义 |
| Edge Functions 执行 | 500,000 次/月 | 5,000,000 次/月 | 自定义 |
| Cron Jobs | 1 个 | 40 个 | 自定义 |
| 团队成员 | 1 人 | 无限 | 无限 |
| 预览部署 | ✅ | ✅ | ✅ |
| 自定义域名 | 50 个 | 200 个 | 自定义 |
| Vercel KV | 3,000 请求/天 | 150,000 请求/天 | 自定义 |
| Vercel Postgres | 256MB 存储 | 512MB 存储 | 自定义 |
| Vercel Blob | 500MB 存储 | 50GB 存储 | 自定义 |
| Analytics | 基础 | 完整 | 完整 |
| 项目数 | 无限(个人) | 无限 | 无限 |
⚠️ 超出免费额度后,Hobby 账户不会自动扣费,服务会被暂停。Pro 账户按量计费。
域名配置详细步骤
添加自定义域名
- 进入项目 → Settings → Domains
- 输入域名(如
www.example.com),点击 Add - 选择重定向方式:
example.com→www.example.com(推荐)www.example.com→example.com
DNS 配置方式
| 方式 | 记录类型 | 主机记录 | 记录值 |
|---|---|---|---|
| 子域名 | CNAME | www | cname.vercel-dns.com |
| 根域名 | A | @ | 76.76.21.21 |
| 根域名(备选) | A | @ | 76.76.21.142 |
Vercel DNS 托管
将域名 Nameservers 改为 Vercel 提供的 DNS 服务器,即可自动配置所有记录。
常见 DNS 服务商配置
Cloudflare: CNAME www → cname.vercel-dns.com(关闭代理/橙色云朵)
阿里云万网: CNAME www → cname.vercel-dns.com
腾讯云 DNSPod: CNAME www → cname.vercel-dns.com构建缓存优化
Vercel 自动缓存 node_modules 和构建输出,可通过以下方式优化:
// vercel.json — 自定义安装和构建命令
{
"framework": "vite",
"buildCommand": "yarn build",
"installCommand": "yarn install --frozen-lockfile"
}# 使用 Turborepo 缓存(Monorepo)
# turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
}
}
}Netlify 也支持类似缓存机制:
# netlify.toml — 缓存优化
[build]
command = "yarn build"
[build.environment]
NETLIFY_CACHE_YARN = "true" # 缓存 yarn 缓存目录
NODE_OPTIONS = "--max-old-space-size=4096" # 增大构建内存| 优化手段 | Vercel | Netlify |
|---|---|---|
| 依赖缓存 | 自动缓存 node_modules | 自动缓存 node_modules |
| 构建输出缓存 | 自动(.next、dist 等) | 自动 |
| Monorepo 缓存 | Turborepo 远程缓存 | Turborepo 远程缓存 |
| 缓存清理 | 控制台 Redeploy(勾选 Clear cache) | 控制台 Deploys → Clear cache and deploy |
Monorepo 支持
Vercel 原生支持 Monorepo,可指定 Root Directory 构建单个包。
monorepo/
├── apps/
│ ├── web/ ← Root Directory 设为 apps/web
│ └── admin/ ← Root Directory 设为 apps/admin
├── packages/
│ └── shared/
├── turbo.json
└── package.json// turbo.json — Turborepo 配置
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}| Monorepo 工具 | Vercel 支持 |
|---|---|
| Turborepo | ✅ 原生(Vercel 开发) |
| pnpm Workspaces | ✅ |
| Yarn Workspaces | ✅ |
| Nx | ✅ |
Netlify
特点
- 静态站点托管先驱
- 支持表单处理、身份认证
- 插件系统丰富
- Split Testing(A/B 测试)
部署方式
方式一:Git 集成
- 登录 netlify.com
- 点击 "Add new site" → "Import an existing project"
- 选择仓库,配置构建命令
方式二:CLI 部署
# 安装 CLI
npm i -g netlify-cli
# 登录
netlify login
# 初始化项目
netlify init
# 部署到预览环境
netlify deploy
# 部署到生产环境
netlify deploy --prod
# 仅部署 Functions
netlify deploy --prod --functions
# 仅部署指定目录
netlify deploy --dir=./distNetlify CLI 常用命令
# 环境变量管理
netlify env:set <name> <value> # 设置环境变量
netlify env:set <name> <value> --scope functions # 仅 Functions 可用
netlify env:import .env # 从文件批量导入环境变量
netlify env:list # 列出所有环境变量
netlify env:unset <name> # 删除环境变量
# 站点管理
netlify open # 在浏览器打开站点
netlify open:admin # 打开 Netlify 控制台
netlify open:site # 打开站点 URL
netlify status # 查看当前站点状态和关联信息
netlify link # 关联本地项目与 Netlify 站点
netlify unlink # 取消关联
# 日志与调试
netlify logs:function <name> # 查看指定函数日志
netlify logs:deploy # 查看部署日志
# 部署管理
netlify deploy:list # 查看部署历史
netlify deploy --trigger # 触发新的构建部署
# 本地开发
netlify dev # 启动本地开发服务器(模拟 Functions + 环境变量)
netlify dev --port 8888 # 指定端口方式三:拖拽部署
- 直接将
dist文件夹拖拽到 Netlify 控制台 - 适合快速测试
netlify.toml 配置
[build]
command = "yarn build"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
# SPA 路由
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
# API 代理
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
# 自定义域名重定向
[[redirects]]
from = "https://old-domain.com/*"
to = "https://new-domain.com/:splat"
status = 301
# 缓存策略
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.html"
[headers.values]
Cache-Control = "no-cache, no-store, must-revalidate"_redirects 和 _headers 文件
除了 netlify.toml,Netlify 还支持在发布目录中使用 _redirects 和 _headers 文件,优先级低于 netlify.toml。
_redirects 文件
# 格式:源路径 目标路径 状态码(可选) 条件(可选)
# SPA 路由回退
/* /index.html 200
# 301 永久重定向
/old-page /new-page 301
# 带条件的重定向(仅特定国家)
/geo-blocked /sorry 302 Country=CN,RU
# 带条件的重定向(仅特定角色,需 Identity)
/admin/* /admin/:splat 200 Role=admin
# 外部代理
/api/* https://api.example.com/:splat 200
# 签名重定向(防止篡改)
/secret-docs /docs 302 Signed=true
# 404 自定义页面
/* /404.html 404💡 文件放在发布目录根目录(如
public/_redirects或dist/_redirects),构建时会自动复制。
_headers 文件
# 格式:
# 路径匹配
# Header-Name: Header-Value
/assets/*
Cache-Control: public, max-age=31536000, immutable
/*.html
Cache-Control: no-cache, no-store, must-revalidate
/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
/api/*
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS| 配置方式 | 优先级 | 适用场景 |
|---|---|---|
netlify.toml | 最高 | 项目级配置,版本控制 |
_redirects / _headers 文件 | 中 | 动态生成或由构建工具输出 |
| UI 控制台 | 最低 | 快速调整,不进代码库 |
表单处理
<!-- Netlify 自动处理表单提交,无需后端 -->
<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact" />
<input type="text" name="name" />
<input type="email" name="email" />
<button type="submit">发送</button>
</form>Netlify Functions
Netlify Functions 基于 AWS Lambda,放置在 netlify/functions/ 目录下。
// netlify/functions/hello.js
exports.handler = async (event, context) => {
const name = event.queryStringParameters?.name || 'World';
return {
statusCode: 200,
body: JSON.stringify({ message: `Hello, ${name}!` }),
};
};// netlify/functions/protected.js — 使用 context.clientContext 获取用户信息
exports.handler = async (event, context) => {
const { user } = context.clientContext || {};
if (!user) {
return { statusCode: 401, body: 'Unauthorized' };
}
return {
statusCode: 200,
body: JSON.stringify({ user }),
};
};# netlify.toml — Functions 配置
[functions]
directory = "netlify/functions"
node_bundler = "esbuild" # 使用 esbuild 打包(更快)
included_files = ["data/**"] # 包含额外文件
external_node_modules = ["sharp"] # 外部模块| 特性 | Netlify Functions | Vercel Functions |
|---|---|---|
| 运行时 | AWS Lambda | 自有基础设施 |
| 最大执行时间 | 10s(免费)/ 26min(Pro) | 10s(免费)/ 60s(Pro) |
| 包大小限制 | 50MB(未压缩) | 250MB |
| TypeScript | ✅ | ✅ |
| Scheduled Functions | ✅(Pro) | ✅(Cron) |
Edge Functions
Netlify Edge Functions 基于 Deno 运行时,部署在全球边缘节点。
// netlify/edge-functions/geo-redirect.ts
import type { Context } from '@netlify/edge-functions';
export default async (request: Request, context: Context) => {
const country = context.geo.country?.code || 'US';
if (country === 'CN') {
return new Response('', {
status: 302,
headers: { Location: '/zh' },
});
}
};
export const config = { path: '/' };// netlify/edge-functions/ab-test.ts
import type { Context } from '@netlify/edge-functions';
export default async (request: Request, context: Context) => {
const bucket = Math.random() < 0.5 ? 'a' : 'b';
context.cookies.set('ab-test', bucket, { path: '/' });
const url = new URL(request.url);
url.pathname = `/variant-${bucket}${url.pathname}`;
return context.rewrite(url);
};
export const config = { path: '/' };# netlify.toml — 注册 Edge Functions
[[edge_functions]]
function = "geo-redirect"
path = "/"
[[edge_functions]]
function = "ab-test"
path = "/landing"| 特性 | Netlify Edge Functions | Vercel Edge Functions |
|---|---|---|
| 运行时 | Deno | Edge Runtime |
| 全球节点数 | 100+ | 100+ |
| 最大执行时间 | 50ms(CPU) | 30s |
| Geo API | context.geo | request.geo |
| KV 存储 | Edge Context | Edge Config |
身份认证(Identity)
Netlify 内置 Identity 服务,无需自建认证系统。
# netlify.toml
[dev]
autoLaunch = false
# Identity 默认开启,无需额外配置// 客户端集成(使用 netlify-identity-widget)
import netlifyIdentity from 'netlify-identity-widget';
netlifyIdentity.init();
// 登录
netlifyIdentity.open('login');
// 注册
netlifyIdentity.open('signup');
// 获取当前用户
const user = netlifyIdentity.currentUser();
// 监听事件
netlifyIdentity.on('init', user => console.log('init', user));
netlifyIdentity.on('login', user => console.log('login', user));
netlifyIdentity.on('logout', () => console.log('logged out'));// Functions 中验证用户
exports.handler = async (event, context) => {
const { user } = context.clientContext;
// user 包含:
// - sub: 用户唯一 ID
// - email: 邮箱
// - app_metadata: 管理员设置的角色等
// - user_metadata: 用户自定义信息
return {
statusCode: 200,
body: JSON.stringify({ userId: user.sub, email: user.email }),
};
};| 功能 | 免费版 | Pro 版 |
|---|---|---|
| 用户数 | 1,000 | 10,000+ |
| GitHub/GitLab 登录 | ✅ | ✅ |
| Google 登录 | ✅ | ✅ |
| 邮箱邀请 | ✅ | ✅ |
| 角色管理 | ❌ | ✅ |
表单处理详细用法
<!-- 通知表单 -->
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field">
<input type="hidden" name="form-name" value="contact" />
<p class="hidden"><input name="bot-field" /></p>
<input type="text" name="name" placeholder="姓名" required />
<input type="email" name="email" placeholder="邮箱" required />
<textarea name="message" placeholder="留言" required></textarea>
<button type="submit">发送</button>
</form>// JavaScript 提交表单
const handleSubmit = async (event) => {
event.preventDefault();
const formData = new FormData(event.target);
await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(formData).toString(),
});
};表单通知集成
在 Netlify 控制台配置表单通知:
| 通知方式 | 配置说明 |
|---|---|
| Settings → Forms → Form notifications → Email notification | |
| Slack | 需要 Webhook URL |
| Discord | 需要 Webhook URL |
| Zapier | 连接第三方服务 |
| Webhook | 自定义 HTTP 端点 |
表单 + Functions 联动
// netlify/functions/submission-created.js
// 表单提交时自动触发
exports.handler = async (event) => {
const payload = JSON.parse(event.body);
const { name, email, message } = payload.data;
// 发送到自定义服务、数据库等
console.log(`新表单提交: ${name} (${email}): ${message}`);
return { statusCode: 200 };
};Split Testing 详细配置
Netlify 原生支持分支级别的流量分割(A/B 测试)。
# 使用 CLI 配置 Split Testing
netlify split:fractions --production-branch main --branch experiment-v2 --fraction 0.3
# 查看当前配置
netlify split:list
# 结束测试并选择获胜分支
netlify split:wins --branch main# netlify.toml — 也可通过配置文件设置
# 通常在控制台操作更方便
# Settings → Split Testing → 选择分支和流量比例| 功能 | 说明 |
|---|---|
| 流量分配 | 自定义比例(如 70/30) |
| 分支支持 | 任意部署分支 |
| Cookie 持久化 | 同一用户始终看到同一版本 |
| GA 集成 | 可配合 Google Analytics 分析 |
Netlify 免费额度详细说明
| 资源 | Starter(免费) | Pro | Enterprise |
|---|---|---|---|
| 带宽 | 100GB/月 | 1TB/月 | 自定义 |
| 构建时间 | 300 分钟/月 | 25,000 分钟/月 | 自定义 |
| Functions 执行 | 125K 请求/月 | 5M 请求/月 | 自定义 |
| Functions 执行时长 | 100GB-Hrs/月 | 500GB-Hrs/月 | 自定义 |
| Forms 提交 | 100 次/月 | 1,000 次/月 | 自定义 |
| Identity 用户 | 1,000 人 | 10,000 人 | 自定义 |
| 团队成员 | 1 人 | 无限 | 无限 |
| 自定义域名 | ✅ | ✅ | ✅ |
| Split Testing | ❌ | ✅ | ✅ |
| 插件数量 | ✅ | ✅ | ✅ |
| Deploy Previews | ✅ | ✅ | ✅ |
| Slack 通知 | ❌ | ✅ | ✅ |
⚠️ 超出免费带宽后站点不会暂停,但会收到警告邮件并要求升级。
Monorepo 支持
Netlify 支持 Monorepo,可在控制台设置 Base Directory 指定构建目录。
# apps/web/netlify.toml
[build]
base = "apps/web"
command = "yarn build"
publish = "dist"
# 或在 monorepo 根目录统一管理
# Settings → Build & deploy → Base directory 设为 apps/web| Monorepo 工具 | Netlify 支持 |
|---|---|
| Turborepo | ✅ |
| pnpm Workspaces | ✅ |
| Yarn Workspaces | ✅ |
| Nx | ✅ |
💡 Netlify 会自动检测 monorepo 中的
netlify.toml。多个子项目需分别创建站点,各自设置 Base Directory。
插件系统
Netlify 支持构建插件,可在构建生命周期中执行自定义逻辑。
# 安装官方插件
npm install -D netlify-plugin-lighthouse
# 安装社区插件
npm install -D netlify-plugin-image-optim# netlify.toml — 注册插件
[[plugins]]
package = "netlify-plugin-lighthouse"
[plugins.inputs]
audits = ["performance", "accessibility", "best-practices"]
[[plugins]]
package = "netlify-plugin-image-optim"
[plugins.inputs]
quality = 80// netlify/plugins/my-plugin/index.js — 自定义插件
module.exports = {
onPreBuild: ({ utils }) => {
console.log('构建前执行...');
},
onBuild: ({ utils }) => {
console.log('构建中执行...');
},
onPostBuild: ({ utils }) => {
console.log('构建后执行...');
},
onSuccess: ({ utils }) => {
console.log('构建成功!');
},
onError: ({ utils }) => {
console.log('构建失败!');
},
};| 热门插件 | 功能 |
|---|---|
| netlify-plugin-lighthouse | Lighthouse 性能审计 |
| netlify-plugin-image-optim | 图片压缩优化 |
| netlify-plugin-subfont | 字体优化 |
| netlify-plugin-checklinks | 死链检测 |
| netlify-plugin-cypress | E2E 测试 |
对比
| 特性 | Vercel | Netlify |
|---|---|---|
| 框架支持 | Next.js 最佳,其他也支持 | 通用 |
| 构建速度 | 快 | 快 |
| Serverless | 原生支持 | 基于 AWS Lambda |
| Edge Functions | 原生(Edge Runtime) | 原生(Deno) |
| 表单处理 | 不支持 | 原生支持 |
| A/B 测试 | 不支持 | 原生 Split Testing |
| 免费带宽 | 100GB/月 | 100GB/月 |
| 免费构建时间 | 6,000 分钟/月 | 300 分钟/月 |
| 免费函数调用 | 100GB-Hrs | 125K 请求 |
| Cron Jobs | ✅(免费 1 个) | ✅(Pro 版) |
| 自定义域名 | ✅ | ✅ |
| 自动 HTTPS | ✅ | ✅ |
| 预览部署 | ✅ | ✅ |
| 插件系统 | ❌ | ✅ |
| 身份认证 | ❌(需第三方) | ✅(内置 Identity) |
| 并发构建上限 | Hobby: 1 / Pro: 3 | Starter: 1 / Pro: 3 |
| Monorepo | ✅ 原生支持 | ✅ 设置 Base Directory |
| 回滚方式 | CLI + 控制台 | CLI + 控制台 |
自定义域名配置
Vercel
- 进入项目 → Settings → Domains
- 添加域名(如
www.example.com) - 按提示配置 DNS:
- CNAME 记录:
www→cname.vercel-dns.com - 或 A 记录:
@→76.76.21.21
- CNAME 记录:
Netlify
- 进入项目 → Domain settings → Add custom domain
- 配置 DNS:
- CNAME 记录:
www→your-site.netlify.app - 或使用 Netlify DNS(推荐)
- CNAME 记录:
常见问题
构建失败
# 检查构建命令是否正确
# 检查 Node 版本
# 检查环境变量是否配置路由 404
// Vercel: vercel.json
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}# Netlify: netlify.toml
[[redirects]]
from = "/*"
to = "/index.html"
status = 200环境变量不生效
# Vite 项目:环境变量必须以 VITE_ 开头
# 确认在平台控制台设置了环境变量
# 确认构建时环境变量可用(不是运行时)其他部署平台对比
Cloudflare Pages
Cloudflare Pages 托管在 Cloudflare 全球 CDN 上,与 Workers 深度集成。
# 使用 Wrangler CLI 部署
npm install -g wrangler
wrangler pages project create my-app
wrangler pages deploy dist --project-name=my-app| 特性 | Cloudflare Pages |
|---|---|
| 免费带宽 | 无限 |
| 构建次数 | 500 次/月 |
| Serverless | Cloudflare Workers |
| 边缘运行时 | V8 Isolates |
| KV 存储 | Workers KV |
| 数据库 | D1(SQLite)、Hyperdrive |
| R2 存储 | S3 兼容对象存储 |
# wrangler.toml — Workers 配置
name = "my-worker"
main = "src/worker.ts"
compatibility_date = "2024-01-01"
[[kv_namespaces]]
binding = "CACHE"
id = "xxxxxxxxxxxx"GitHub Pages
GitHub Pages 适合个人项目和文档站点,完全免费但功能有限。
# .github/workflows/deploy.yml
name: Deploy to GitHub Pages
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install && npm run build
- uses: actions/upload-pages-artifact@v3
with:
path: dist
- uses: actions/deploy-pages@v4| 特性 | GitHub Pages |
|---|---|
| 费用 | 完全免费 |
| 带宽限制 | 100GB/月 |
| 构建 | 仅支持 Actions 或 gh-pages 分支 |
| Serverless | ❌ |
| 自定义域名 | ✅ |
| HTTPS | ✅ |
Gitee Pages
Gitee Pages(Gitee Pages 服务)适合国内访问,但需实名认证。
| 特性 | Gitee Pages |
|---|---|
| 费用 | 免费(需实名认证) |
| 访问速度 | 国内快 |
| 自定义域名 | ❌(仅支持 gitee.io) |
| HTTPS | ✅ |
| 自动部署 | ❌(需手动更新) |
| 限制 | 内容审核、仓库公开 |
Railway
Railway 提供全栈部署能力,支持数据库、消息队列等后端服务。
# 安装 CLI
npm install -g @railway/cli
# 登录
railway login
# 初始化项目
railway init
# 部署
railway up| 特性 | Railway |
|---|---|
| 免费额度 | $5/月 |
| 数据库 | PostgreSQL、MySQL、Redis、MongoDB |
| Docker 支持 | ✅ |
| 自定义域名 | ✅ |
| 自动 HTTPS | ✅ |
| Cron Jobs | ✅ |
Render
Render 提供 Web 服务、静态站点、数据库、Cron Jobs 等全栈能力。
# render.yaml
services:
- type: web
name: my-app
runtime: node
buildCommand: yarn install && yarn build
startCommand: yarn start
envVars:
- key: DATABASE_URL
fromDatabase:
name: my-db
property: connectionString
databases:
- name: my-db
plan: free| 特性 | Render |
|---|---|
| 免费额度 | 静态站点免费,Web 服务 750h/月 |
| 数据库 | PostgreSQL(免费 90 天) |
| Docker 支持 | ✅ |
| Cron Jobs | ✅ |
| 自定义域名 | ✅ |
腾讯云 Webify
腾讯云 Webify 针对国内前端项目优化,支持备案域名。
| 特性 | 腾讯云 Webify |
|---|---|
| 费用 | 免费额度有限 |
| 国内访问 | ✅(需备案) |
| 框架支持 | Vue/React/Next.js/Nuxt |
| 自定义域名 | ✅(需备案) |
| CDN | 腾讯云 CDN |
阿里云 Function Compute
阿里云函数计算适合 Serverless 全栈部署。
| 特性 | 阿里云 Function Compute |
|---|---|
| 计费方式 | 按量付费 |
| 运行时 | Node.js/Python/Java/Go |
| API 网关 | ✅ |
| 对象存储 | OSS |
| 自定义域名 | ✅(需备案) |
平台选择建议
| 场景 | 推荐平台 |
|---|---|
| 个人博客/文档 | GitHub Pages、Vercel |
| 国内访问优先 | 腾讯云 Webify、阿里云 FC |
| 全栈应用 | Railway、Render |
| 高流量站点 | Cloudflare Pages(无限带宽) |
| Next.js 项目 | Vercel(官方支持最佳) |
| 预算敏感 | Cloudflare Pages、GitHub Pages |
部署最佳实践
预览部署 + 评论机器人
预览部署让 PR 审查更高效,评论机器人自动在 PR 中发布预览链接。
# GitHub Actions — 自定义预览部署通知
name: Preview Deploy
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install && npm run build
- name: Deploy to Vercel Preview
run: npx vercel --token=${{ secrets.VERCEL_TOKEN }}
- name: Comment PR
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '🚀 预览部署完成!\n\nhttps://preview-pr-${{ github.event.number }}.vercel.app'
});部署 Hooks(部署钩子)
部署钩子允许通过 HTTP 请求触发部署,适合外部系统联动。
Vercel Deploy Hooks
- 进入项目 → Settings → Git → Deploy Hooks
- 填入 Hook Name 和目标分支(如
main) - 生成的 URL 格式:
https://api.vercel.com/v1/integrations/deploy/prj_xxxx/yyyy
# 通过 curl 触发部署
curl -X POST "https://api.vercel.com/v1/integrations/deploy/prj_xxxx/yyyy"Netlify Deploy Hooks
- 进入项目 → Site configuration → Build & deploy → Build hooks
- 填入名称和目标分支
- 生成的 URL 格式:
https://api.netlify.com/build_hooks/xxxx
# 通过 curl 触发部署
curl -X POST "https://api.netlify.com/build_hooks/xxxx"| 场景 | Vercel | Netlify |
|---|---|---|
| CMS 内容更新触发 | Deploy Hook | Build Hook |
| 定时重建 | Cron + Deploy Hook | Build Hook + 外部定时器 |
| 数据库变更触发 | Webhook → Deploy Hook | Webhook → Build Hook |
| GitHub Action 触发 | API + Token | API + Token |
并发构建限制
| 计划 | Vercel | Netlify |
|---|---|---|
| 免费版 | 1 个并发构建 | 1 个并发构建 |
| Pro 版 | 3 个并发构建 | 3 个并发构建 |
| Enterprise | 自定义 | 自定义 |
💡 Monorepo 项目中多个子项目同时推送到同一仓库时,超出并发限制的构建会排队等待。
部署通知集成
// Vercel Deploy Hook — 部署成功后通知
// 在 Vercel 控制台 Settings → Git → Deploy Hooks 创建
// Webhook 接收端
app.post('/deploy-webhook', (req, res) => {
const { type, payload } = req.body;
if (type === 'deployment.succeeded') {
sendSlackNotification(`部署成功: ${payload.deployment.url}`);
} else if (type === 'deployment.error') {
sendSlackNotification(`部署失败: ${payload.deployment.meta}`);
}
res.status(200).send('OK');
});# Netlify — 构建通知
# Settings → Build & deploy → Deploy notifications
# 支持 Email、Slack、Discord、Webhook回滚策略
Vercel 回滚详细步骤
UI 操作:
- 进入项目 → Deployments
- 找到目标历史部署,点击进入详情
- 点击右上角 "..." → "Promote to Production"
- 确认后立即生效
CLI 操作:
# 查看部署历史
vercel ls
# 回滚到上一次生产部署
vercel rollback
# 回滚到指定部署(需先通过 vercel ls 获取 URL)
vercel promote https://my-app-xxxxx.vercel.appGit 回滚:
# 回滚代码后重新部署
git revert HEAD
git push origin main
# Vercel 自动触发重新部署Netlify 回滚详细步骤
UI 操作:
- 进入项目 → Deploys
- 找到目标历史部署
- 点击 "Publish deploy" 按钮
- 确认后立即生效
CLI 操作:
# 查看部署历史
netlify deploy:list
# 回滚到指定部署(通过 deploy ID)
netlify api updateDeploy --data '{"deploy_id":"<deploy-id>","state":"ready"}'Git 回滚:
git revert HEAD
git push origin main
# Netlify 自动触发重新部署| 平台 | UI 回滚 | CLI 回滚 | Git 回滚 |
|---|---|---|---|
| Vercel | Deployments → Promote to Production | vercel rollback / vercel promote | revert + push |
| Netlify | Deploys → Publish deploy | netlify deploy:list | revert + push |
💡 两个平台的 UI 回滚都是即时生效的,不需要重新构建。Git 回滚会触发新的构建流程。
性能监控
# Vercel — 使用 Speed Insights
npm install @vercel/speed-insights
# Netlify — 使用 Lighthouse 插件
npm install -D netlify-plugin-lighthouse| 监控指标 | 工具 | 说明 |
|---|---|---|
| Core Web Vitals | Vercel Analytics / PageSpeed Insights | LCP、FID、CLS |
| 构建时间 | 平台控制台 | 监控构建耗时趋势 |
| 错误率 | Sentry / LogRocket | 前端异常监控 |
| 可用性 | UptimeRobot / Better Uptime | 站点可用性监控 |
| 函数冷启动 | 平台控制台 + 自定义日志 | Serverless 性能优化参考 |
// 自定义性能监控(轻量方案)
if (typeof window !== 'undefined') {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'largest-contentful-paint') {
console.log('LCP:', entry.startTime);
}
if (entry.entryType === 'layout-shift') {
console.log('CLS:', entry.value);
}
}
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
observer.observe({ type: 'layout-shift', buffered: true });
}