WebP reduces JPEG file sizes by 25-35% at equivalent visual quality. In Ruby, the image_processing gem is the modern choice — it wraps libvips (fast, memory-efficient) and is already a dependency in Rails 6+ Active Storage. MiniMagick is the older alternative. Both need system libraries installed.
Method 1: image_processing gem (Rails Active Storage standard)
image_processing wraps both libvips and ImageMagick. The Vips processor is faster and uses less memory than Magick for large images.
gem install image_processing
# Or in Gemfile:
# gem 'image_processing'
bundle install
# System deps
apt install libvips libvips-tools # for Vips processor
apt install imagemagick # for MiniMagick processorrequire 'image_processing/vips'
# Single file
pipeline = ImageProcessing::Vips
.source('photo.jpg')
.convert('webp')
.saver(Q: 80) # Q = quality, 0-100
result = pipeline.call(destination: 'photo.webp')
puts "Output: #{result.path}"
# With resizing
resized_webp = ImageProcessing::Vips
.source('photo.jpg')
.resize_to_limit(1200, 900) # max 1200x900, preserves aspect ratio
.convert('webp')
.saver(Q: 80)
.call(destination: 'photo-resized.webp')
# Batch conversion
require 'pathname'
def batch_jpg_to_webp(src_dir, out_dir, quality: 80)
Pathname(out_dir).mkpath
converted = 0
Dir.glob(File.join(src_dir, '*.{jpg,jpeg}')).each do |src|
out = File.join(out_dir, File.basename(src, '.*') + '.webp')
ImageProcessing::Vips
.source(src)
.convert('webp')
.saver(Q: quality)
.call(destination: out)
converted += 1
end
converted
end
n = batch_jpg_to_webp('./images', './images/webp')
puts "Converted #{n} images"
libvips is significantly faster than ImageMagick for WebP encoding — about 3-5x on large images. If performance matters for batch processing, prefer the Vips processor.
Method 2: MiniMagick (familiar API, ImageMagick backend)
MiniMagick is the older Rails image processing standard. It wraps ImageMagick and handles WebP well.
gem install mini_magick
# System deps
apt install imagemagick libwebp-devrequire 'mini_magick'
def jpg_to_webp(src_path, out_path, quality: 80)
image = MiniMagick::Image.open(src_path)
image.format 'webp'
image.quality quality
# Strip EXIF metadata — reduces size and removes GPS/device info
image.strip
image.write out_path
end
jpg_to_webp('photo.jpg', 'photo.webp')
puts 'Done'
# MiniMagick with explicit options
MiniMagick::Tool::Convert.new do |convert|
convert << 'photo.jpg'
convert.define 'webp:lossless=false'
convert.define 'webp:method=6' # 0=fastest, 6=best compression
convert.quality '80'
convert << 'photo.webp'
end
The define 'webp:method=6' option tunes the WebP encoder — higher values trade speed for better compression. Method 6 is the slowest but produces the smallest files. For batch conversions where speed matters, use method 4 (default) or lower.
Method 3: ChangeThisFile API (Net::HTTP, no system libs)
The API handles WebP conversion server-side. Source auto-detected from filename — pass target=webp. Free tier: 1,000 conversions/month, no card needed.
require 'net/http'
require 'uri'
require 'securerandom'
API_KEY = 'ctf_sk_your_key_here'
def jpg_to_webp_api(src_path, out_path)
uri = URI('https://changethisfile.com/v1/convert')
boundary = "CTF#{SecureRandom.hex(8)}"
file_data = File.binread(src_path)
body = [
"--#{boundary}\r\n",
'Content-Disposition: form-data; name="file"; filename="' + File.basename(src_path) + "\"\r\n",
"Content-Type: image/jpeg\r\n\r\n",
file_data, "\r\n",
"--#{boundary}\r\n",
"Content-Disposition: form-data; name=\"target\"\r\n\r\n",
"webp\r\n",
"--#{boundary}--\r\n"
].join
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{API_KEY}"
req['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
req.body = body
resp = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 60) { |h| h.request(req) }
raise "API error: #{resp.code}" unless resp.code == '200'
File.binwrite(out_path, resp.body)
end
jpg_to_webp_api('photo.jpg', 'photo.webp')
puts 'Done'
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| image_processing (Vips) | Rails 6+ Active Storage, fast batch conversion | Requires libvips system package |
| MiniMagick | Older Rails apps, familiar ImageMagick API | Slower than Vips for large batches; needs ImageMagick + libwebp |
| ChangeThisFile API | No system libs, Heroku/PaaS without buildpacks | Network call per image; free tier 25MB limit |
Production tips
- Prefer image_processing/vips over image_processing/mini_magick. libvips is 3-5x faster for WebP encoding and uses significantly less memory. The API is the same — just change the require.
- Strip EXIF data on conversion. JPEG photos often contain GPS coordinates, camera model, and other metadata that users may not want published. Strip it with image.strip in MiniMagick or via the Vips processor.
- Serve WebP with Accept header detection. Check request.headers['Accept']&.include?('image/webp') in Rails. All modern browsers send this header. Return the WebP version to supporting clients, JPEG to others.
- Use Active Storage variants in Rails. config.active_storage.variant_processor = :vips in application.rb enables automatic WebP output: user.avatar.variant(format: :webp, quality: 80).processed.
- Don't delete the original JPEG. Re-encoding from WebP introduces additional quality loss. Keep both formats and serve the appropriate one.
For Rails apps, image_processing with the Vips processor is the modern standard — it's already a dependency and handles WebP natively. MiniMagick is fine for existing apps. Free API tier: 1,000 conversions/month.