Generating Gzipped Assets for Nginx in Rails 4.2
Nginx has the ability to serve static .gz files instead of compressing assets on the fly, which is great for performance. Unfortunately, Rails recently dropped its support of this feature. Here’s how to restore it.
Background
In older versions of Rails (specifically, before Sprockets 3 was introduced), you may have noticed that assets:precompile
generated .gz
versions of all the assets. A properly-configured Nginx web server can serve these gzipped versions instead of the uncompressed originals.
This is a great feature, because:
- Browsers get compressed responses, which means faster transfer times and happier users
- Since the gzipped version is pre-generated, Nginx doesn’t need to perform compression on the fly
- Furthermore, we can use gzip’s maximum compression setting, since we don’t have to pay the CPU penalty on each web request
Unfortunately, this gzip feature was removed from the asset pipeline in Sprockets 3, and this generated a lot of discussion on GitHub. The most insightful comments can be found on the commit itself, and there has been some good feedback in a subsequent feature request.
As it turns out, the pre-generated gzip feature works great with Nginx, but causes subtle bugs with the Apache web server, which is also popular. Rather than enable by default a feature that breaks a significant portion of web servers, the Sprockets team decided not support the feature at all.
The good news is that the Rails team has stepped in and is planning to bring the feature back via an opt-in at some point. But what can we do in the meantime?
Implementing a workaround
If you use Nginx, chances are you want this feature back. Concerned developers have put forth various workarounds.
The solution I use is to extend the assets:precompile
rake task to perform the gzipping. Here’s how I’ve implemented the rake task (which, by the way, is baked into any Rails app generated with my Rails template):
# Place this code in lib/tasks/assets.rake
namespace :assets do
desc "Create .gz versions of assets"
task :gzip => :environment do
zip_types = /\.(?:css|html|js|otf|svg|txt|xml)$/
public_assets = File.join(
Rails.root,
"public",
Rails.application.config.assets.prefix)
Dir["#{public_assets}/**/*"].each do |f|
next unless f =~ zip_types
mtime = File.mtime(f)
gz_file = "#{f}.gz"
next if File.exist?(gz_file) && File.mtime(gz_file) >= mtime
File.open(gz_file, "wb") do |dest|
gz = Zlib::GzipWriter.new(dest, Zlib::BEST_COMPRESSION)
gz.mtime = mtime.to_i
IO.copy_stream(open(f), gz)
gz.close
end
File.utime(mtime, mtime, gz_file)
end
end
# Hook into existing assets:precompile task
Rake::Task["assets:precompile"].enhance do
Rake::Task["assets:gzip"].invoke
end
end
Note that:
- The
.gz
is only created if the original is newer - It’s important to set the right modification time for the generated
.gz
- Only “compressable” file extensions are considered (e.g. not images)
Configuring Nginx
Finally, it is worth a reminder that all of this gzip work is effective only if Nginx is properly configured. Specifically, the gzip_static
directive is that magic that instructs Nginx to look for the matching .gz
files when an asset is requested.
Here’s the proper config, which you can also find mentioned in the old 4.1.x version of the asset pipeline documentation:
location ~ ^/(assets)/ {
gzip_static on;
}
You just read
Generating Gzipped Assets for Nginx in Rails 4.2
Nginx has the ability to serve static .gz files instead of compressing assets on the fly, which is great for performance. Unfortunately, Rails recently dropped its support of this feature. Here’s how to restore it.
Share this post? Copy link
About the author
Hi! I’m a Ruby and CSS enthusiast, regular open source contributor, software engineer, and occasional blogger writing from the San Francisco Bay Area. Thanks for stopping by! —Matt