Appearance
Netlify 部署 - 现代 Web 开发平台
1. Netlify 简介
1.1 什么是 Netlify
Netlify 是一个为现代 Web 项目提供托管和无服务器后端服务的云平台,专注于 Jamstack 架构,提供持续部署、表单处理、无服务器函数等功能。
核心特性:
- 持续部署:Git 集成,自动构建和部署
- 全球 CDN:智能内容分发网络
- 表单处理:无需后端的表单提交
- 无服务器函数:Netlify Functions
- 边缘处理:Edge Functions
- 分支部署:每个分支独立部署
- 拆分测试:A/B 测试内置支持
1.2 支持的框架
✓ React(Create React App、Vite、Next.js)
✓ Vue(Vue CLI、Nuxt.js、Vite)
✓ Angular
✓ Svelte(SvelteKit)
✓ Gatsby
✓ Hugo
✓ Jekyll
✓ 静态 HTML1.3 定价模式
Starter(免费版)
├── 100GB 带宽/月
├── 300 构建分钟/月
├── 无限站点
└── 表单提交 100次/月
Pro(专业版)$19/月
├── 1TB 带宽/月
├── 25,000 构建分钟/月
├── 后台函数 125K 次/月
└── 表单提交 1000次/月
Business(商业版)$99/月
├── 自定义带宽
├── 优先构建
├── 角色权限
└── 审计日志2. 快速开始
2.1 从 Git 仓库部署
步骤 1:连接仓库
bash
# 登录 Netlify
https://app.netlify.com
# 点击 "Add new site" -> "Import an existing project"
# 选择 Git 提供商
- GitHub
- GitLab
- Bitbucket
- Azure DevOps
# 选择仓库
搜索并选择要部署的仓库步骤 2:配置构建设置
bash
# 基本设置
Branch to deploy: main
# 构建设置
Base directory: (留空或指定子目录)
Build command: npm run build
Publish directory: dist
# 环境变量(可选)
添加必要的环境变量步骤 3:部署
bash
# 点击 "Deploy site" 按钮
Netlify 会:
1. 克隆代码
2. 安装依赖
3. 运行构建命令
4. 发布到 CDN
5. 生成唯一 URL2.2 使用 Netlify CLI
安装 CLI
bash
# 使用 npm
npm install -g netlify-cli
# 使用 pnpm
pnpm add -g netlify-cli
# 使用 yarn
yarn global add netlify-cli
# 验证安装
netlify --version登录和初始化
bash
# 登录 Netlify
netlify login
# 初始化项目
netlify init
# 选择部署方式
? What would you like to do?
> Create & configure a new site
Link this directory to an existing site部署项目
bash
# 构建并部署
netlify deploy
# 部署到生产环境
netlify deploy --prod
# 打开站点
netlify open2.3 拖放部署
bash
# 最简单的方式:拖放
1. 访问 https://app.netlify.com/drop
2. 将构建后的文件夹拖到页面
3. 立即部署3. 配置文件
3.1 netlify.toml
基础配置
toml
# netlify.toml
[build]
publish = "dist"
command = "npm run build"
[build.environment]
NODE_VERSION = "18"
NPM_VERSION = "9"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Content-Type-Options = "nosniff"完整配置
toml
[build]
base = ""
publish = "dist"
command = "npm run build"
functions = "netlify/functions"
edge_functions = "netlify/edge-functions"
[build.environment]
NODE_VERSION = "18.17.0"
NPM_VERSION = "9.6.7"
RUBY_VERSION = "2.7.2"
[build.processing]
skip_processing = false
[build.processing.css]
bundle = true
minify = true
[build.processing.js]
bundle = true
minify = true
[build.processing.html]
pretty_urls = true
[build.processing.images]
compress = true
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
[[redirects]]
from = "/old-page"
to = "/new-page"
status = 301
force = true
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
conditions = {Country = ["US", "GB"]}
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/*.js"
[headers.values]
Cache-Control = "public, max-age=604800"
[[headers]]
for = "/api/*"
[headers.values]
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "GET, POST, PUT, DELETE"
[context.production]
command = "npm run build"
[context.production.environment]
VITE_API_URL = "https://api.example.com"
[context.deploy-preview]
command = "npm run build:preview"
[context.deploy-preview.environment]
VITE_API_URL = "https://api-preview.example.com"
[context.branch-deploy]
command = "npm run build:staging"3.2 _redirects 文件
bash
# public/_redirects
# SPA 回退
/* /index.html 200
# API 代理
/api/* https://api.example.com/:splat 200
# 重定向
/old-blog/* /blog/:splat 301
# 国家重定向
/ /us 302 Country=us
/ /uk 302 Country=gb
# 语言重定向
/ /en 302 Language=en
/ /fr 302 Language=fr
# 角色访问控制
/admin/* /admin/login 401! Role=admin3.3 _headers 文件
bash
# public/_headers
# 全局 headers
/*
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
# 静态资源缓存
/assets/*
Cache-Control: public, max-age=31536000, immutable
# API CORS
/api/*
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
# CSP
/
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';4. React 应用部署
4.1 Vite + React
项目配置
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'router': ['react-router-dom']
}
}
}
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});netlify.toml 配置
toml
[build]
publish = "dist"
command = "npm run build"
[build.environment]
NODE_VERSION = "18"
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
force = true
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"4.2 Create React App
toml
# netlify.toml
[build]
publish = "build"
command = "npm run build"
[build.environment]
REACT_APP_API_URL = "https://api.example.com"
CI = "true"
[[redirects]]
from = "/*"
to = "/index.html"
status = 2004.3 环境变量
在 Netlify 控制台配置
bash
# Site settings -> Environment variables
# 添加变量
VITE_API_URL=https://api.example.com
VITE_GA_ID=UA-XXXXXXXXX-X
# 作用域
- Production
- Deploy previews
- Branch deploys在代码中使用
typescript
// Vite 项目
const apiUrl = import.meta.env.VITE_API_URL;
// Create React App
const apiUrl = process.env.REACT_APP_API_URL;
// 在构建时注入
console.log('API URL:', apiUrl);.env 文件
bash
# .env.production
VITE_API_URL=https://api.example.com
VITE_DEBUG=false
# .env.development
VITE_API_URL=http://localhost:3000
VITE_DEBUG=true5. Netlify Functions
5.1 创建函数
目录结构
project/
├── netlify/
│ └── functions/
│ ├── hello.js
│ ├── users.js
│ └── auth/
│ └── login.js
├── src/
└── netlify.toml基础函数
javascript
// netlify/functions/hello.js
exports.handler = async (event, context) => {
const { name = 'World' } = event.queryStringParameters || {};
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString()
})
};
};TypeScript 函数
typescript
// netlify/functions/users.ts
import { Handler, HandlerEvent, HandlerContext } from '@netlify/functions';
interface User {
id: string;
name: string;
email: string;
}
const handler: Handler = async (
event: HandlerEvent,
context: HandlerContext
) => {
try {
const users: User[] = [
{ id: '1', name: 'John', email: 'john@example.com' },
{ id: '2', name: 'Jane', email: 'jane@example.com' }
];
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({ users })
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal Server Error' })
};
}
};
export { handler };5.2 访问数据库
javascript
// netlify/functions/db-query.js
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false }
});
exports.handler = async (event) => {
try {
const { rows } = await pool.query('SELECT * FROM users');
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ users: rows })
};
} catch (error) {
console.error('Database error:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
};5.3 后台函数(Background Functions)
javascript
// netlify/functions/send-email.js
exports.handler = async (event) => {
// 立即返回响应
const response = {
statusCode: 202,
body: JSON.stringify({ message: 'Email queued' })
};
// 异步处理(最多运行 10 分钟)
sendEmail(JSON.parse(event.body))
.then(() => console.log('Email sent'))
.catch(err => console.error('Email error:', err));
return response;
};
async function sendEmail(data) {
// 发送邮件逻辑
await new Promise(resolve => setTimeout(resolve, 5000));
}5.4 定时函数(Scheduled Functions)
javascript
// netlify/functions/daily-report.js
// 每天凌晨 1 点执行
const schedule = '@daily';
exports.handler = async () => {
console.log('Running daily report...');
// 生成报告
const report = await generateDailyReport();
// 发送报告
await sendReport(report);
return {
statusCode: 200,
body: JSON.stringify({ message: 'Report sent' })
};
};
exports.schedule = schedule;6. Edge Functions
6.1 创建 Edge Function
typescript
// 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';
const countryUrls = {
'US': '/en-us',
'GB': '/en-gb',
'FR': '/fr',
'DE': '/de'
};
const url = new URL(request.url);
const redirectPath = countryUrls[country] || '/en';
// 如果已经在正确的路径,继续
if (url.pathname.startsWith(redirectPath)) {
return;
}
// 重定向到对应国家的版本
return Response.redirect(new URL(redirectPath, request.url));
};
export const config = {
path: "/"
};6.2 A/B 测试
typescript
// netlify/edge-functions/ab-test.ts
import type { Context } from "@netlify/edge-functions";
export default async (request: Request, context: Context) => {
// 从 cookie 获取或生成变体
const cookie = context.cookies.get('ab-test-variant');
const variant = cookie || (Math.random() < 0.5 ? 'a' : 'b');
// 设置 cookie
if (!cookie) {
context.cookies.set({
name: 'ab-test-variant',
value: variant,
path: '/',
maxAge: 30 * 24 * 60 * 60 // 30 天
});
}
// 添加 header
const response = await context.next();
response.headers.set('X-AB-Test-Variant', variant);
return response;
};
export const config = {
path: "/*"
};6.3 认证保护
typescript
// netlify/edge-functions/auth.ts
import type { Context } from "@netlify/edge-functions";
export default async (request: Request, context: Context) => {
const url = new URL(request.url);
// 检查是否有认证 token
const token = context.cookies.get('auth-token');
// 如果访问 /admin 且没有 token,重定向到登录
if (url.pathname.startsWith('/admin') && !token) {
return Response.redirect(new URL('/login', request.url));
}
// 验证 token(简化示例)
if (token) {
try {
// 验证 JWT token
const isValid = await validateToken(token);
if (!isValid) {
return Response.redirect(new URL('/login', request.url));
}
} catch (error) {
return Response.redirect(new URL('/login', request.url));
}
}
return context.next();
};
async function validateToken(token: string): Promise<boolean> {
// Token 验证逻辑
return true;
}
export const config = {
path: "/admin/*"
};7. 表单处理
7.1 基础表单
html
<!-- public/index.html -->
<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact" />
<div>
<label for="name">Name:</label>
<input type="text" id="name" name="name" required />
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required />
</div>
<div>
<label for="message">Message:</label>
<textarea id="message" name="message" required></textarea>
</div>
<button type="submit">Submit</button>
</form>7.2 React 表单
typescript
// src/components/ContactForm.tsx
import { useState, FormEvent } from 'react';
export function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
const formBody = new URLSearchParams({
'form-name': 'contact',
...formData
});
try {
await fetch('/', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formBody.toString()
});
alert('Form submitted successfully!');
setFormData({ name: '', email: '', message: '' });
} catch (error) {
alert('Error submitting form');
}
};
return (
<form
name="contact"
method="POST"
data-netlify="true"
onSubmit={handleSubmit}
>
<input type="hidden" name="form-name" value="contact" />
<input
type="text"
name="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
/>
<input
type="email"
name="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
<textarea
name="message"
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
required
/>
<button type="submit">Submit</button>
</form>
);
}7.3 文件上传
html
<form name="upload" method="POST" data-netlify="true" enctype="multipart/form-data">
<input type="hidden" name="form-name" value="upload" />
<input type="file" name="file" required />
<button type="submit">Upload</button>
</form>7.4 表单通知
toml
# netlify.toml
[[plugins]]
package = "@netlify/plugin-emails"
[plugins.inputs]
from = "noreply@example.com"
to = "admin@example.com"
subject = "New form submission"8. 分支部署
8.1 配置分支部署
toml
# netlify.toml
[context.production]
command = "npm run build"
[context.production.environment]
VITE_API_URL = "https://api.example.com"
[context.deploy-preview]
command = "npm run build:preview"
[context.deploy-preview.environment]
VITE_API_URL = "https://api-preview.example.com"
[context.branch-deploy]
command = "npm run build:dev"
[context.staging]
command = "npm run build:staging"
[context.staging.environment]
VITE_API_URL = "https://api-staging.example.com"8.2 分支子域名
bash
# 在 Netlify 控制台启用
Site settings -> Build & deploy -> Branch deploys
# 分支部署 URL 格式
<branch-name>--<site-name>.netlify.app
示例:
staging--myapp.netlify.app
feature-login--myapp.netlify.app9. 自定义域名
9.1 添加域名
bash
# 方式 1:通过 Netlify 控制台
Domain settings -> Add custom domain
# 方式 2:通过 CLI
netlify domains:add example.com9.2 DNS 配置
Netlify DNS(推荐)
bash
# 使用 Netlify 的 DNS 服务器
dns1.p01.nsone.net
dns2.p01.nsone.net
dns3.p01.nsone.net
dns4.p01.nsone.net
# Netlify 会自动配置:
- A 记录
- CNAME 记录
- SSL 证书外部 DNS
bash
# CNAME 记录(子域名)
Type: CNAME
Name: www
Value: <site-name>.netlify.app
# A 记录(根域名)
Type: A
Name: @
Value: 75.2.60.5
# AAAA 记录(IPv6)
Type: AAAA
Name: @
Value: 2600:1901:0:7e84::9.3 域名重定向
toml
# netlify.toml
[[redirects]]
from = "https://www.example.com/*"
to = "https://example.com/:splat"
status = 301
force = true10. CI/CD 集成
10.1 GitHub Actions
yaml
# .github/workflows/netlify-deploy.yml
name: Deploy to Netlify
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
- run: npm run build
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v2
with:
publish-dir: './dist'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: 'Deploy from GitHub Actions'
enable-pull-request-comment: true
enable-commit-comment: true
overwrites-pull-request-comment: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}10.2 GitLab CI
yaml
# .gitlab-ci.yml
image: node:18
stages:
- build
- deploy
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
deploy:
stage: deploy
only:
- main
script:
- npm install -g netlify-cli
- netlify deploy --prod --dir=dist
variables:
NETLIFY_AUTH_TOKEN: $NETLIFY_AUTH_TOKEN
NETLIFY_SITE_ID: $NETLIFY_SITE_ID11. 性能优化
11.1 资源优化
toml
# netlify.toml
[build.processing]
skip_processing = false
[build.processing.css]
bundle = true
minify = true
[build.processing.js]
bundle = true
minify = true
[build.processing.html]
pretty_urls = true
[build.processing.images]
compress = true11.2 缓存策略
bash
# _headers
/assets/*
Cache-Control: public, max-age=31536000, immutable
/*.js
Cache-Control: public, max-age=604800
/*.css
Cache-Control: public, max-age=604800
/index.html
Cache-Control: public, max-age=0, must-revalidate11.3 预渲染
toml
# netlify.toml
[[plugins]]
package = "@netlify/plugin-nextjs"
[[plugins]]
package = "netlify-plugin-submit-sitemap"
[plugins.inputs]
baseUrl = "https://example.com"
sitemapPath = "/sitemap.xml"12. 监控和分析
12.1 Netlify Analytics
bash
# 启用 Analytics
Site settings -> Analytics -> Enable
# 查看数据
- 页面浏览量
- 访问来源
- 热门页面
- 404 错误12.2 日志查看
bash
# 使用 CLI 查看日志
netlify logs
# 实时日志
netlify logs --live
# 函数日志
netlify functions:log <function-name>13. 最佳实践
13.1 项目结构
my-netlify-app/
├── netlify/
│ ├── functions/
│ │ └── api.js
│ └── edge-functions/
│ └── geo.ts
├── public/
│ ├── _redirects
│ ├── _headers
│ └── assets/
├── src/
│ ├── components/
│ └── pages/
├── netlify.toml
└── package.json13.2 安全配置
toml
# netlify.toml
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
X-XSS-Protection = "1; mode=block"
Referrer-Policy = "strict-origin-when-cross-origin"
Permissions-Policy = "geolocation=(), microphone=(), camera=()"
Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload"13.3 环境管理
bash
# 使用不同环境
- Production: main 分支
- Staging: staging 分支
- Development: develop 分支
# 环境变量分离
- 生产环境变量
- 预览部署变量
- 分支部署变量14. 总结
Netlify 提供了:
- 简单部署:Git 集成,自动化构建
- 强大功能:Functions、Edge Functions、Forms
- 全球加速:CDN 和边缘网络
- 开发工具:分支部署、预览部署
- 灵活配置:丰富的配置选项
通过合理使用 Netlify 的功能,可以快速部署和管理现代 Web 应用。