You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails多子域名场景下Sprockets读取本地Manifest而非CDN文件的问题

为什么你的config.assets.manifest配置不生效?

我来帮你拆解一下问题的核心,以及对应的解决思路:

核心原因:config.assets.manifest不支持HTTP URL,也不是请求级别的配置

你这里犯了两个关键的误解:

  1. config.assets.manifest只接受本地文件系统路径,不支持远程URL
    Sprockets的manifest机制是基于本地文件的——它需要读取本地的.sprockets-manifest-xxx.json文件来建立「原始资产路径」和「带digest的文件名」的映射关系。你把它设置成CDN的HTTP URL,Rails/Sprockets根本无法识别,直接忽略这个配置,继续使用默认的public/assets目录下的本地manifest。

  2. config.assets.manifest是全局初始化配置,不支持请求级别的动态切换
    和asset_host不同,config.assets.manifest的配置是在Rails启动时就确定的,它并不接受带request参数的Proc。你写的Proc其实根本不会被调用,因为Sprockets在初始化时就会解析这个配置,而不是在每个请求到来时执行。

针对多子域名场景的解决方案

既然每个子域名对应CDN上独立的资产目录和manifest,我们需要换一种思路:在请求时动态选择对应的manifest来生成资产路径。这里有两种可行的方案:

方案一:本地维护每个子域名的manifest,请求时动态加载

适合你已经把每个子域名的manifest同步到本地的情况:

  1. 预加载所有子域名的manifest
    在config/initializers/assets.rb中,提前加载每个子域名对应的本地manifest:

    # 假设你的子域名列表可以从配置或数据库获取
    SUBDOMAIN_MANIFESTS = {}
    ['client_a', 'client_b'].each do |subdomain|
      # 找到该子域名目录下最新的manifest文件
      manifest_glob = Rails.root.join("public/assets/#{subdomain}/.sprockets-manifest-*.json")
      latest_manifest_path = Dir[manifest_glob].max_by { |f| File.mtime(f) }
      if latest_manifest_path
        SUBDOMAIN_MANIFESTS[subdomain] = Sprockets::Manifest.new(Rails.application.assets, latest_manifest_path)
      end
    end
    
  2. 覆盖资产路径生成的helper方法
    在app/helpers/application_helper.rb中,重写asset_path方法,根据当前子域名选择对应的manifest:

    def asset_path(source, options = {})
      # 如果当前请求有子域名,且我们预加载了对应的manifest
      if request.subdomain.present? && SUBDOMAIN_MANIFESTS.key?(request.subdomain)
        manifest = SUBDOMAIN_MANIFESTS[request.subdomain]
        digested_asset = manifest.lookup(source)
        if digested_asset
          # 拼接CDN路径
          protocol = request.ssl? ? 'https' : 'http'
          "#{protocol}://cdn.domain.com/#{request.subdomain}/#{digested_asset}"
        else
          # 找不到对应资产时,回退到默认逻辑
          super
        end
      else
        # 没有子域名时,使用默认逻辑
        super
      end
    end
    

方案二:动态拉取CDN上的manifest(适合不想同步本地的情况)

如果不想把每个子域名的manifest同步到本地,可以直接从CDN拉取,但要注意加缓存避免性能损耗:

在app/helpers/application_helper.rb中添加:

def remote_manifest(subdomain)
  # 缓存manifest内容1小时,避免每次请求都去CDN拉取
  Rails.cache.fetch("asset_manifest/#{subdomain}", expires_in: 1.hour) do
    protocol = request.ssl? ? 'https' : 'http'
    manifest_url = "#{protocol}://cdn.domain.com/#{subdomain}/.sprockets-manifest.json"
    # 这里用HTTParty举例,你也可以用Net::HTTP或其他HTTP客户端
    response = HTTParty.get(manifest_url)
    response.success? ? JSON.parse(response.body) : nil
  end
end

def asset_path(source, options = {})
  if request.subdomain.present?
    manifest = remote_manifest(request.subdomain)
    if manifest && manifest['assets'].key?(source)
      protocol = request.ssl? ? 'https' : 'http'
      "#{protocol}://cdn.domain.com/#{request.subdomain}/#{manifest['assets'][source]}"
    else
      super
    end
  else
    super
  end
end

注意:要确保CDN上的.sprockets-manifest.json可以被你的应用服务器访问到,并且做好缓存策略,避免频繁请求CDN。

内容的提问来源于stack exchange,提问作者Cameron

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:05:08