Skip to main content

Vercel部署实践

部署准备

项目优化

1. 构建优化

// next.config.js
module.exports = {
swcMinify: true,
images: {
domains: ['your-image-domain.com'],
},
experimental: {
optimizeCss: true,
optimizeImages: true,
},
}

2. 性能检查

# 运行性能检查
npm run build
npm run analyze # 需要安装@next/bundle-analyzer

# 检查构建输出
ls -lh .next/static/chunks/

3. 环境变量配置

# .env.production
NEXT_PUBLIC_API_URL=https://api.yourblog.com
NEXT_PUBLIC_SITE_URL=https://yourblog.com
DATABASE_URL=your_database_url

部署流程

初始部署

1. 连接仓库

  1. 登录Vercel控制台
  2. 点击"New Project"
  3. 导入Git仓库
  4. 配置项目设置

2. 部署配置

// vercel.json
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"routes": [
{
"src": "/api/.*",
"headers": {
"Access-Control-Allow-Origin": "*"
}
}
]
}

自动部署配置

GitHub Actions配置

# .github/workflows/deploy.yml
name: Deploy to Vercel
on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Install Dependencies
run: npm ci

- name: Run Tests
run: npm test

- name: Deploy to Vercel
if: success()
run: npx vercel --token ${VERCEL_TOKEN}
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}

分支预览

// scripts/comment-on-pr.js
async function commentOnPR(deployment) {
const { GITHUB_TOKEN, PR_NUMBER } = process.env;
const comment = `
🚀 Preview deployment is ready!
URL: ${deployment.url}
Branch: ${deployment.git.branch}
Commit: ${deployment.git.commitSha}
`;

// 使用GitHub API发送评论
// ...
}

域名配置

DNS设置

添加域名

  1. 在Vercel项目设置中添加域名
  2. 配置DNS记录:
# A记录
Type: A
Name: @
Value: 76.76.21.21

# CNAME记录
Type: CNAME
Name: www
Value: cname.vercel-dns.com

SSL配置

{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains"
}
]
}
]
}

监控和优化

性能监控

Web Vitals监控

// lib/analytics.ts
import { Analytics } from '@vercel/analytics';

export function reportWebVitals({ id, name, label, value }) {
Analytics.track(name, {
id,
label,
value,
page: window.location.pathname,
});
}

性能预算设置

// performance-budget.json
{
"resourceSizes": [
{
"resourceType": "script",
"budget": 300
},
{
"resourceType": "total",
"budget": 1000
}
],
"resourceCounts": [
{
"resourceType": "third-party",
"budget": 10
}
]
}

错误监控

Sentry集成

// lib/sentry.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
environment: process.env.NODE_ENV,
integrations: [
new Sentry.Integrations.BrowserTracing({
tracingOrigins: ['localhost', 'yourblog.com'],
}),
],
});

export function captureException(error: Error, context?: any) {
Sentry.withScope((scope) => {
if (context) {
scope.setExtras(context);
}
Sentry.captureException(error);
});
}

错误边界组件

// components/ErrorBoundary.tsx
import React from 'react';
import { captureException } from '@/lib/sentry';

interface Props {
children: React.ReactNode;
fallback?: React.ReactNode;
}

interface State {
hasError: boolean;
}

export class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError() {
return { hasError: true };
}

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
captureException(error, errorInfo);
}

render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="error-container">
<h2>Something went wrong</h2>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}

return this.props.children;
}
}

缓存优化

CDN缓存配置

// pages/_middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
const response = NextResponse.next();

// 静态资源缓存
if (request.nextUrl.pathname.startsWith('/_next/static/')) {
response.headers.set(
'Cache-Control',
'public, max-age=31536000, immutable'
);
}

// API响应缓存
if (request.nextUrl.pathname.startsWith('/api/')) {
response.headers.set(
'Cache-Control',
'public, s-maxage=60, stale-while-revalidate=300'
);
}

return response;
}

静态生成优化

// lib/cache.ts
import { redis } from './redis';

export async function getCachedData(key: string) {
const cached = await redis.get(key);
if (cached) {
return JSON.parse(cached);
}
return null;
}

export async function setCachedData(
key: string,
data: any,
ttl: number = 3600
) {
await redis.setex(key, ttl, JSON.stringify(data));
}