ruby / fingerprint
I use file-based asset fingerprinting in Ruby web apps to enable aggressive caching with CDNs.
The approach
As part of the deployment build, after building assets with esbuild, a Rake task fingerprints the files:
require "digest"
namespace :assets do
task :precompile do
["public/css/app.css", "public/js/app.js"].each do |old_path|
hash = Digest::MD5.file(File.expand_path(old_path, __dir__))
ext = File.extname(old_path)
base = old_path.chomp(ext)
new_path = "#{base}-#{hash}#{ext}"
system "mv #{old_path} #{new_path}"
end
end
end
The renamed files are served from public/:
public/app.css -> public/app-a1b2c3d4.css
public/app.js -> public/app-a1b2c3d5.js
Rack configuration
Serve static files with Rack::Static
middleware
and set cache headers per path:
use Rack::Static,
urls: ["/css", "/js", "/favicon.ico"],
root: "public",
header_rules: [
# fingerprinted by rake assets:precompile
["/css", {"Cache-Control" => "public, max-age=31536000, immutable"}],
["/js", {"Cache-Control" => "public, max-age=31536000, immutable"}],
# not fingerprinted
["/favicon.ico", {"Cache-Control" => "public, max-age=86400"}]
]
Since filenames include content hashes, each URL is immutable. Browsers and CDNs can cache aggressively (1 year) without risk of serving stale content.
The immutable directive eliminates revalidation requests
even on page reload.
Deployment
Example build command for Render:
npm install && \
npm run build && \
bundle install && \
bundle exec rake db:migrate && \
bundle exec rake assets:precompile
This:
- Installs JavaScript dependencies
- Builds and bundles with esbuild
- Installs Ruby dependencies
- Migrates the database
- Fingerprints static assets
Template integration
At boot, resolve fingerprinted paths:
# see rake assets:precompile definition in Rakefile
# and ui/views/layouts/application.haml
app_css_path = "/css/app.css"
app_js_path = "/js/app.js"
if ["staging", "production"].include?(ENV.fetch("APP_ENV"))
root = File.expand_path("../..", __dir__)
css_path = Dir.glob("#{root}/public/css/app*.css")&.first
if css_path
app_css_path = css_path.split("public")[1]
end
js_path = Dir.glob("#{root}/public/js/app*.js")&.first
if js_path
app_js_path = js_path.split("public")[1]
end
end
APP_CSS_PATH = app_css_path.freeze
APP_JS_PATH = app_js_path.freeze
In views such as ui/views/layouts/application.haml:
%link{ rel: "stylesheet", href: APP_CSS_PATH }
%script{ src: APP_JS_PATH }