如何对不同平台生成不同的 _redirects 规则

解读

_redirects 文件是 Netlify、Vercel 等国内常用静态托管平台的“路由重写/跳转”配置。在前端交付流水线里,同一份源码往往要部署到 测试环境预发布环境正式环境 甚至 私有化集群,而每个平台的域名、HTTPS 强制策略、CDN 前缀、API 网关地址都不一样,因此 _redirects 内容必须“同构不同份”。Grunt 作为老牌任务运行器,优势在于“配置即代码”,可以把“平台差异”抽象成 多份模板 + 变量注入,在构建阶段一次性生成目标平台的最终规则,避免人肉维护多份文件导致的“配置漂移”。

知识点

  1. Grunt 任务阶段:init-config → registerTask → run;在 init-config 里把“平台”作为维度,先拼装数据,再交给模板插件渲染。
  2. 模板引擎:grunt-template、grunt-mustache、grunt-ejs 均可,国内团队更倾向 grunt-template,因为语法就是 Lodash 模板,无额外学习成本。
  3. 变量注入:通过 grunt.option('env') 或 grunt.option('platform') 读取命令行参数,例如 grunt build --platform=netlify-prod,在 Gruntfile 里映射到对应 JSON 数据片段。
  4. 文件生成:grunt-contrib-copy + grunt-contrib-rename 组合,或者直接用 grunt-file-creator 把渲染后的字符串写入 dist/_redirects;注意 Windows 与 Linux 换行差异,用 line endings: 'lf' 保证托管平台识别。
  5. 多平台差异
    • Netlify:支持 200/301/302/404/!,可带 Country、Role 条件;
    • Vercel:重定向写在 vercel.json,格式不同,需要同构转换;
    • 腾讯云静态托管:只识别纯 301/302,不支持边缘规则;
    • 私有化 Nginx:需要生成 rewrite.conf 而非 _redirects。
      因此“一份模板,多种出口”是核心思路。
  6. 校验与回滚:用 grunt-contrib-clean 先清旧文件,再用 grunt-validate-redirects(社区插件)做 语法校验,防止把错误规则推上线导致全站 404。
  7. CI 集成:在 GitHub Actions / Gitee Go / Jenkins 里把 platform 作为矩阵变量,并行产出多份产物,最后由发布脚本 scp/ossutil/coscli 上传到对应 bucket。

答案

  1. 在 Gruntfile.js 顶部声明平台映射表:
const platformRules = {
  'netlify-prod': [
    '/api/* https://api.xxx.com/:splat 200',
    '/* /index.html 200'
  ],
  'vercel-prod': [
    { source: '/api/(.*)', destination: 'https://api.xxx.com/$1', statusCode: 200 },
    { source: '/(.*)', destination: '/index.html', statusCode: 200 }
  ],
  'tc-cloud': [
    '/api/* https://api.xxx.com/:splat 301',
    '/* /index.html 301'
  ]
};
  1. 安装并加载插件:
npm i -D grunt-template grunt-contrib-clean grunt-file-creator
  1. 配置任务:
module.exports = function(grunt) {
  const platform = grunt.option('platform') || 'netlify-prod';
  const rules = platformRules[platform];

  grunt.initConfig({
    clean: { redirects: ['dist/_redirects'] },

    template: {
      options: { data: { rules: rules.join('\n') } },
      'netlify-prod': {
        files: [{ src: 'redirects.tpl', dest: 'dist/_redirects' }]
      }
    },

    // 若目标平台是 Vercel,则把数组转成 JSON
    'file-creator': {
      'vercel-prod': {
        'dist/vercel.json': function(fs, fd, done) {
          fs.writeSync(fd, JSON.stringify({ redirects: rules }, null, 2));
          done();
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-template');
  grunt.loadNpmTasks('grunt-file-creator');

  grunt.registerTask('redirects', function() {
    if (platform.startsWith('vercel')) {
      return grunt.task.run(['clean:redirects', 'file-creator:vercel-prod']);
    }
    return grunt.task.run(['clean:redirects', 'template:netlify-prod']);
  });

  grunt.registerTask('build', ['clean', 'webpack:prod', 'redirects']);
};
  1. 模板文件 redirects.tpl(放在项目根目录):
# 平台:<%= platform %>
# 生成时间:<%= grunt.template.today() %>
<%= rules %>
  1. 运行:
grunt build --platform=netlify-prod   # 生成 dist/_redirects
grunt build --platform=vercel-prod    # 生成 dist/vercel.json
  1. 在 package.json 中封装脚本,方便 CI 调用:
"scripts": {
  "build:prod": "grunt build --platform=netlify-prod",
  "build:pre": "grunt build --platform=tc-cloud"
}

通过以上步骤,即可实现“一份源码,多条流水线,各自产出平台专属的重定向规则”,且全程可追踪、可回滚、可校验。

拓展思考

  1. 灰度场景:如果要在 _redirects 里加入 按 Cookie 或 Header 分流(Netlify 支持 Role 条件),可以把灰度名单维护在远端 Redis,构建前通过 grunt-http 拉取,再注入模板,实现“配置热更新”而无需重新打包静态资源。
  2. 多语言站点:国内业务常区分 简体、繁体、英文,可在平台维度之外再叠加 locale 维度,使用 grunt build --platform=netlify-prod --locale=zh-cn,模板里用双重循环生成 /zh-cn/* /zh-cn/index.html 200 这类规则,避免手写 30 条重复配置。
  3. 安全合规:私有化部署时,客户要求 不能出现真实后台地址,可在 Gruntfile 里通过 node-env-encrypt 对目标地址做对称加密,生成 _redirects 后由运维在服务器端用 Lua 解密,实现“地址脱敏”。
  4. 性能极限:当规则数量 >1000 条 时,Netlify 会降级为边缘函数,冷启动增加 50~80 ms。此时可把规则拆成 哈希分区,用 Grunt 生成多个 _redirects 片段,再由 Nginx include 合并,既兼容托管平台,又保证性能。