Cloudflare Workers + R2部署Hugo增强解决方案

Cloudflare Workers + R2部署Hugo增强解决方案

Cloudflare Workers + R2部署Hugo网站,突破Workers文件数上限增强解决方案

下面我将详细解释文件位置和扩展功能实现,并提供完整的配置示例:

文件位置说明

  1. worker.jswrangler.toml

    • 放在 Hugo 项目根目录(与 config.toml 同级)
    • 上传到 R2 存储桶
    • 这些是 Worker 的配置文件,只在部署时使用
  2. R2 存储桶内容

    • 只包含 Hugo 生成的 public 目录中的文件
    • 通过命令行或脚本上传,无需手动操作
your-hugo-project/
├── archetypes/
├── content/
├── layouts/
├── public/          # Hugo 生成的静态文件(上传到 R2)
├── static/
├── worker.js        # Worker 脚本
├── wrangler.toml    # 配置文件
└── config.toml      # Hugo 配置

扩展版 wrangler.toml(增强功能)

name = "hugo-site-worker"
main = "worker.js"
compatibility_date = "2024-06-25"
node_compat = true  # 启用 Node.js 兼容模式

# R2 存储桶配置
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "hugo-site-assets"  # 替换为你的桶名

# 环境变量(多环境支持)
[vars]
GA_ID = "UA-XXXXX-1"  # Google Analytics ID
SITE_URL = "https://yourdomain.com"

# 部署环境配置(生产/预览)
[env.production]
name = "hugo-site-prod"
vars = { ENV = "production" }

[env.preview]
name = "hugo-site-preview"
vars = { ENV = "preview" }

# 自定义域绑定
routes = [
  { pattern = "yourdomain.com/*", zone_id = "your-zone-id" },
  { pattern = "www.yourdomain.com/*", zone_id = "your-zone-id" }
]

# 构建命令(自动构建时使用)
[build]
command = "npm run build"
upload_dir = "public"

扩展版 worker.js(增强功能)

// ===== 配置区 =====
const CONFIG = {
  // SEO 优化
  trailingSlash: false,  // 是否强制尾部斜杠
  
  // 安全头
  securityHeaders: {
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",
    "X-XSS-Protection": "1; mode=block",
    "Referrer-Policy": "strict-origin-when-cross-origin",
    "Permissions-Policy": "geolocation=(), microphone=(), camera=()",
  },
  
  // 缓存策略
  cacheRules: {
    html: "public, max-age=600",          // 10分钟
    data: "public, max-age=3600",         // 1小时
    assets: "public, max-age=2592000",    // 30天
  },
  
  // 重定向规则
  redirects: {
    "/old-page": "/new-page",
    "/blog/2020/": "/archive/",
  },
  
  // 多语言支持
  languages: ["en", "zh"],
  defaultLanguage: "en",
};

// ===== 工具函数 =====
// 获取内容类型
const getContentType = (filename) => {
  const extMap = {
    html: "text/html; charset=utf-8",
    css: "text/css",
    js: "application/javascript",
    json: "application/json",
    png: "image/png",
    jpg: "image/jpeg",
    jpeg: "image/jpeg",
    gif: "image/gif",
    svg: "image/svg+xml",
    ico: "image/x-icon",
    txt: "text/plain",
    webp: "image/webp",
    webm: "video/webm",
    mp4: "video/mp4",
    pdf: "application/pdf",
    woff: "font/woff",
    woff2: "font/woff2",
    xml: "application/xml",
  };
  
  const ext = filename.split(".").pop().toLowerCase();
  return extMap[ext] || "application/octet-stream";
};

// 获取缓存策略
const getCacheControl = (filename) => {
  if (filename.endsWith(".html")) return CONFIG.cacheRules.html;
  if (filename.match(/\.(json|xml|rss)$/)) return CONFIG.cacheRules.data;
  return CONFIG.cacheRules.assets;
};

// ===== 主处理器 =====
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    let pathname = url.pathname;
    
    // 1. 处理重定向
    if (CONFIG.redirects[pathname]) {
      return Response.redirect(CONFIG.redirects[pathname], 301);
    }
    
    // 2. 处理语言重定向
    const langMatch = pathname.match(/^\/(en|zh)(\/|$)/);
    const acceptLanguage = request.headers.get("Accept-Language") || "";
    
    if (!langMatch && CONFIG.languages.length > 1) {
      const preferredLang = acceptLanguage.includes("zh") ? "zh" : CONFIG.defaultLanguage;
      return Response.redirect(`/${preferredLang}${pathname}`, 302);
    }
    
    // 3. 处理尾部斜杠
    if (CONFIG.trailingSlash && !pathname.endsWith("/") && !pathname.includes(".")) {
      return Response.redirect(`${pathname}/`, 301);
    }
    
    // 4. 处理根路径
    if (pathname === "/") pathname = "/index.html";
    
    // 5. 生成对象名称
    let objectName = pathname.replace(/^\//, "");
    
    // 6. 尝试获取文件
    try {
      let object = await env.ASSETS.get(objectName);
      
      // 7. 备用路径:index.html
      if (!object && !objectName.endsWith("index.html")) {
        const altPath = objectName.endsWith("/") 
          ? `${objectName}index.html` 
          : `${objectName}/index.html`;
        
        object = await env.ASSETS.get(altPath);
        if (object) objectName = altPath;
      }
      
      // 8. 处理404
      if (!object) {
        const notFound = await env.ASSETS.get("404.html");
        return notFound 
          ? buildResponse(notFound, "404.html", 404) 
          : new Response("Not Found", { status: 404 });
      }
      
      // 9. 构建响应
      return buildResponse(object, objectName);
      
    } catch (err) {
      // 10. 错误处理
      return new Response("Internal Server Error", { status: 500 });
    }
    
    // 响应构建器
    function buildResponse(object, filename, status = 200) {
      const headers = new Headers();
      
      // 内容类型
      headers.set("Content-Type", getContentType(filename));
      
      // 缓存控制
      headers.set("Cache-Control", getCacheControl(filename));
      
      // 安全头
      Object.entries(CONFIG.securityHeaders).forEach(([key, value]) => {
        headers.set(key, value);
      });
      
      // 添加环境变量
      if (env.ENV === "preview") {
        headers.set("X-Robots-Tag", "noindex");
      }
      
      // 添加Google Analytics(可选)
      if (filename.endsWith(".html") && env.GA_ID) {
        const gaScript = `
          <script async src="https://www.googletagmanager.com/gtag/js?id=${env.GA_ID}"></script>
          <script>
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '${env.GA_ID}');
          </script>`;
        
        let body = object.body;
        
        // 如果是HTML,注入GA代码
        if (getContentType(filename).startsWith("text/html")) {
          body = body.replace(/<\/head>/i, `${gaScript}</head>`);
        }
        
        return new Response(body, { headers, status });
      }
      
      return new Response(object.body, { headers, status });
    }
  }
};

增强功能说明

  1. 多环境支持

    • 生产环境:wrangler deploy --env production
    • 预览环境:wrangler deploy --env preview
    • 自动添加 X-Robots-Tag: noindex 到预览环境
  2. SEO 优化

    • 自动语言重定向(基于浏览器语言首选项)
    • 尾部斜杠统一处理
    • 301/302 重定向支持
    • 自动注入 Google Analytics
  3. 安全增强

    • 完整的安全头设置(CSP, X-Frame-Options 等)
    • 内容类型嗅探防护
    • 权限策略控制
  4. 智能缓存

    • HTML:短缓存(10分钟)
    • 数据文件:中等缓存(1小时)
    • 静态资源:长缓存(30天)
  5. 错误处理

    • 自定义404页面
    • 错误捕获和日志
    • 备用路径自动查找

部署脚本 deploy.sh

#!/bin/bash

# 1. 清理并构建Hugo网站
hugo --minify --cleanDestinationDir

# 2. 上传到R2
echo "Uploading files to R2..."
wrangler r2 object put hugo-site-assets/ --directory ./public --recursive

# 3. 部署Worker(根据环境变量)
if [ "$ENV" = "production" ]; then
  echo "Deploying to production..."
  wrangler deploy --env production
else
  echo "Deploying to preview..."
  wrangler deploy --env preview
fi

# 4. 清除缓存(可选)
echo "Purging Cloudflare cache..."
curl -X POST "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/purge_cache" \
  -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"purge_everything":true}'

echo "Deployment complete!"

高级功能扩展思路

自定义 Worker 逻辑(比如复杂的 URL 重写、A/B 测试、动态注入等)

  1. A/B 测试
// worker.js 中添加
const AB_TESTS = {
  "/": {
    variants: [
      { weight: 50, path: "/index-v1.html" },
      { weight: 50, path: "/index-v2.html" }
    ]
  }
};

// 在请求处理中
if (AB_TESTS[pathname]) {
  const test = AB_TESTS[pathname];
  const random = Math.random() * 100;
  let cumulative = 0;
  
  for (const variant of test.variants) {
    cumulative += variant.weight;
    if (random <= cumulative) {
      objectName = variant.path.replace(/^\//, "");
      break;
    }
  }
}
  1. API 代理
// 添加API代理规则
if (pathname.startsWith("/api/")) {
  const apiUrl = `https://your-backend.com${pathname.replace('/api', '')}`;
  const apiResponse = await fetch(apiUrl, {
    headers: request.headers,
    method: request.method,
    body: request.body
  });
  
  // 添加CORS头
  const response = new Response(apiResponse.body, apiResponse);
  response.headers.set("Access-Control-Allow-Origin", "*");
  return response;
}
  1. 访客分析
// 在响应后执行
ctx.waitUntil(logVisit(request));

async function logVisit(request) {
  const url = new URL(request.url);
  const data = {
    timestamp: new Date().toISOString(),
    path: url.pathname,
    country: request.cf.country,
    ua: request.headers.get("user-agent"),
    ip: request.headers.get("cf-connecting-ip")
  };
  
  // 存储到R2(或Workers KV)
  await env.ASSETS.put(`logs/${Date.now()}.json`, JSON.stringify(data));
}

部署流程总结

  1. 初始设置
npm install -g wrangler
wrangler login
wrangler r2 bucket create hugo-site-assets
  1. 日常部署
# 设置环境变量
export ENV=production
export CLOUDFLARE_API_TOKEN=your_token

# 执行部署
chmod +x deploy.sh
./deploy.sh
  1. 调试技巧
# 本地测试
wrangler dev

# 查看日志
wrangler tail

这种配置提供了企业级的 Hugo 部署方案,解决了文件数量限制问题,同时增加了高级功能如多环境部署、A/B 测试、API 代理和访客分析,完全利用 Cloudflare 边缘网络的优势。