跳转到内容

搜索

Astro 部署完全指南

8 分钟读完

详细介绍 Astro 应用的各种部署方式,包括静态部署和 SSR 部署的最佳实践

Astro 部署完全指南

本教程将详细介绍如何将 Astro 应用部署到各种平台,包括静态托管和服务端渲染的配置。

部署前准备

1. 构建配置优化

// astro.config.ts
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import vercel from '@astrojs/vercel/serverless';
import netlify from '@astrojs/netlify/functions';
 
export default defineConfig({
  // 根据部署目标选择输出模式
  output: 'static', // 'static' | 'server' | 'hybrid'
  
  // 根据部署平台选择适配器
  adapter: process.env.DEPLOY_TARGET === 'vercel' ? vercel() :
           process.env.DEPLOY_TARGET === 'netlify' ? netlify() :
           process.env.DEPLOY_TARGET === 'node' ? node({ mode: 'standalone' }) :
           undefined,
  
  build: {
    // 生产环境优化
    inlineStylesheets: 'auto',
    split: true,
    excludeMiddleware: false,
  },
  
  // 站点配置
  site: 'https://your-domain.com',
  base: '/', // 如果部署在子路径,修改此值
  
  // 服务端渲染配置
  server: {
    port: 3000,
    host: true
  }
});

2. 环境变量配置

# .env.production
PUBLIC_SITE_URL=https://your-domain.com
PUBLIC_API_BASE_URL=https://api.your-domain.com
 
# 私有环境变量(服务端)
DATABASE_URL=postgresql://...
API_SECRET_KEY=your-secret-key
SMTP_PASSWORD=your-smtp-password
// src/env.d.ts
/// <reference types="astro/client" />
 
interface ImportMetaEnv {
  readonly PUBLIC_SITE_URL: string;
  readonly PUBLIC_API_BASE_URL: string;
  readonly DATABASE_URL: string;
  readonly API_SECRET_KEY: string;
  readonly SMTP_PASSWORD: string;
}
 
interface ImportMeta {
  readonly env: ImportMetaEnv;
}

静态部署

1. Netlify 部署

netlify.toml 配置:

[build]
  command = "npm run build"
  publish = "dist"
 
[build.environment]
  NODE_VERSION = "18"
  NPM_FLAGS = "--prefix=/opt/buildhome/.nodejs/bin/"
 
# 重定向规则
[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200
 
[[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"
    Referrer-Policy = "strict-origin-when-cross-origin"
 
[[headers]]
  for = "/assets/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

GitHub Actions 自动部署:

# .github/workflows/deploy-netlify.yml
name: Deploy to Netlify
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Build
        run: npm run build
        env:
          PUBLIC_SITE_URL: ${{ secrets.PUBLIC_SITE_URL }}
          
      - name: Deploy to Netlify
        uses: netlify/actions/cli@master
        with:
          args: deploy --prod --dir=dist
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

2. Vercel 部署

vercel.json 配置:

{
  "buildCommand": "npm run build",
  "outputDirectory": "dist",
  "devCommand": "npm run dev",
  "installCommand": "npm install",
  "framework": "astro",
  "rewrites": [
    {
      "source": "/api/(.*)",
      "destination": "/api/$1"
    }
  ],
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    }
  ]
}

3. GitHub Pages 部署

# .github/workflows/deploy-github-pages.yml
name: Deploy to GitHub Pages
 
on:
  push:
    branches: [main]
  workflow_dispatch:
 
permissions:
  contents: read
  pages: write
  id-token: write
 
concurrency:
  group: "pages"
  cancel-in-progress: false
 
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
          
      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v4
        
      - name: Install dependencies
        run: npm ci
        
      - name: Build
        run: npm run build
        env:
          PUBLIC_SITE_URL: ${{ steps.pages.outputs.origin }}
          
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./dist
          
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

服务端渲染 (SSR) 部署

1. Node.js 服务器部署

Dockerfile:

# 多阶段构建
FROM node:18-alpine AS builder
 
WORKDIR /app
 
# 复制依赖文件
COPY package*.json ./
RUN npm ci --only=production
 
# 复制源代码并构建
COPY . .
RUN npm run build
 
# 生产镜像
FROM node:18-alpine AS runner
 
WORKDIR /app
 
# 创建非 root 用户
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 astro
 
# 复制构建产物
COPY --from=builder --chown=astro:nodejs /app/dist ./dist
COPY --from=builder --chown=astro:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=astro:nodejs /app/package.json ./package.json
 
# 切换到非 root 用户
USER astro
 
EXPOSE 3000
 
ENV HOST=0.0.0.0
ENV PORT=3000
 
CMD ["node", "./dist/server/entry.mjs"]

docker-compose.yml:

version: '3.8'
 
services:
  astro-app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
      - API_SECRET_KEY=${API_SECRET_KEY}
    restart: unless-stopped
    
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
    depends_on:
      - astro-app
    restart: unless-stopped

Nginx 配置:

# nginx.conf
events {
    worker_connections 1024;
}
 
http {
    upstream astro_app {
        server astro-app:3000;
    }
    
    # 启用 gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
    
    server {
        listen 80;
        server_name your-domain.com;
        
        # 重定向到 HTTPS
        return 301 https://$server_name$request_uri;
    }
    
    server {
        listen 443 ssl http2;
        server_name your-domain.com;
        
        # SSL 配置
        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
        ssl_prefer_server_ciphers off;
        
        # 静态资源缓存
        location /assets/ {
            proxy_pass http://astro_app;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }
        
        # 代理到 Astro 应用
        location / {
            proxy_pass http://astro_app;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }
    }
}

2. Serverless 部署

AWS Lambda (使用 SST):

// sst.config.ts
import { SSTConfig } from "sst";
import { AstroSite } from "sst/constructs";
 
export default {
  config(_input) {
    return {
      name: "astro-app",
      region: "us-east-1",
    };
  },
  stacks(app) {
    app.stack(function Site({ stack }) {
      const site = new AstroSite(stack, "site", {
        path: ".",
        buildCommand: "npm run build",
        environment: {
          PUBLIC_SITE_URL: process.env.PUBLIC_SITE_URL!,
          DATABASE_URL: process.env.DATABASE_URL!,
        },
        customDomain: {
          domainName: "your-domain.com",
          hostedZone: "your-domain.com",
        },
      });
      
      stack.addOutputs({
        SiteUrl: site.url,
      });
    });
  },
} satisfies SSTConfig;

部署优化策略

1. 构建优化

// scripts/optimize-build.ts
import { execSync } from 'child_process';
import { readFileSync, writeFileSync } from 'fs';
import { gzipSync } from 'zlib';
 
// 构建前清理
execSync('rm -rf dist');
 
// 执行构建
execSync('npm run build');
 
// 分析构建产物
const distFiles = execSync('find dist -type f -name "*.js" -o -name "*.css"', { encoding: 'utf8' })
  .split('\n')
  .filter(Boolean);
 
console.log('构建产物分析:');
distFiles.forEach(file => {
  const content = readFileSync(file);
  const gzipped = gzipSync(content);
  console.log(`${file}: ${content.length} bytes (${gzipped.length} bytes gzipped)`);
});

2. 缓存策略

// middleware/cache-headers.ts
import type { MiddlewareHandler } from 'astro';
 
export const cacheHeaders: MiddlewareHandler = async (context, next) => {
  const response = await next();
  const { pathname } = context.url;
  
  // 静态资源长期缓存
  if (pathname.startsWith('/assets/')) {
    response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
  }
  // HTML 页面短期缓存
  else if (pathname.endsWith('.html') || !pathname.includes('.')) {
    response.headers.set('Cache-Control', 'public, max-age=3600, must-revalidate');
  }
  // API 响应缓存
  else if (pathname.startsWith('/api/')) {
    response.headers.set('Cache-Control', 'public, max-age=300');
  }
  
  return response;
};

3. 监控和日志

// utils/monitoring.ts
export class DeploymentMonitor {
  private static instance: DeploymentMonitor;
  
  static getInstance() {
    if (!this.instance) {
      this.instance = new DeploymentMonitor();
    }
    return this.instance;
  }
  
  logDeployment(version: string, environment: string) {
    console.log(`Deployment started: ${version} to ${environment}`);
    
    // 发送到监控服务
    fetch('/api/monitoring/deployment', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        version,
        environment,
        timestamp: new Date().toISOString(),
        status: 'started'
      })
    });
  }
  
  logError(error: Error, context: string) {
    console.error(`Deployment error in ${context}:`, error);
    
    // 发送错误报告
    fetch('/api/monitoring/error', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        error: error.message,
        stack: error.stack,
        context,
        timestamp: new Date().toISOString()
      })
    });
  }
}

部署检查清单

✅ 部署前检查

  • 环境变量配置正确
  • 构建命令测试通过
  • 静态资源路径正确
  • API 端点配置正确
  • SSL 证书配置(生产环境)

✅ 性能优化

  • 启用 gzip/brotli 压缩
  • 配置 CDN
  • 设置适当的缓存头
  • 优化图片和字体
  • 启用 HTTP/2

✅ 安全配置

  • 设置安全头
  • 配置 CORS
  • 隐藏服务器信息
  • 定期更新依赖
  • 配置防火墙规则

✅ 监控和维护

  • 设置健康检查
  • 配置错误监控
  • 设置性能监控
  • 配置日志收集
  • 建立备份策略

通过遵循这个完整的部署指南,你将能够成功地将 Astro 应用部署到各种平台,并确保应用的稳定性和性能。