SoSoraEndo2026年5月9日1 min477 字
なぜ自動派生を回すのか
記事に画像を入れるとき、必要なバリアントは概ね決まっている:
- thumb — 一覧カード用、200×200 or 400×250 程度
- eyecatch — 記事ヒーロー用、960×540 程度
- og — SNS シェア用、1200×630 固定
毎回手動で書き出すのは面倒だし、忘れる。アップロード時に自動で派生を作る 仕組みを 1 度組めば、後は元画像を投げるだけになる。
私は CarrierWave + MiniMagick で組んでいる。シンプルに動く。
基底アップローダ
# app/uploaders/base_uploader.rb
class BaseUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file # 開発: ローカル / 本番: :fog で S3 に切替
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def filename
@name ||= "#{secure_token}.#{file.extension}" if original_filename.present?
end
protected
def secure_token
var = :"@#{mounted_as}_secure_token"
model.instance_variable_get(var) ||
model.instance_variable_set(var, SecureRandom.uuid)
end
end
secure_token で UUID ベースのファイル名にする。元のファイル名から推測されない。
variant 定義
class MediaAssetFileUploader < BaseUploader
process :auto_orient, :strip
version :thumb do
process resize_to_fill: [400, 250, gravity: 'Center']
process quality: 80
process convert: 'webp'
end
version :eyecatch do
process resize_to_fit: [960, 540]
process quality: 85
process convert: 'webp'
end
version :og do
process resize_to_fill: [1200, 630, gravity: 'Center']
process quality: 85
process convert: 'jpg' # OGP は webp 非対応 SNS が多い
end
def extension_allowlist
%w[jpg jpeg png webp gif]
end
end
ポイント 3 つ:
auto_orientで iPhone 撮影画像の回転メタデータを反映するstripで EXIF メタデータを削除(プライバシー + サイズ削減)ogだけは jpg で出す。Facebook / X の OGP サムネイル生成が webp 非対応のことがある
OGP は jpg、それ以外は webp
| バリアント | フォーマット | 理由 |
|---|---|---|
| original | 元のまま | アーカイブとして残す |
| thumb | webp | サイト内のみ。サイズ最小化 |
| eyecatch | webp | サイト内のみ。Next/image が AVIF 変換 |
| og | jpg | 外部 SNS スクレイパに優しく |
モデル側
class MediaAsset < ApplicationRecord
mount_uploader :file, MediaAssetFileUploader
end
これだけ。あとは media_asset.file.thumb.url media_asset.file.eyecatch.url media_asset.file.og.url でアクセスできる。
アップロード API
# Bot::MediaController#create
def create
asset = MediaAsset.new(file: params[:file], alt: params[:alt])
asset.save!
render json: {
id: asset.id,
urls: {
original: asset.file.url,
thumb: asset.file.thumb.url,
eyecatch: asset.file.eyecatch.url,
og: asset.file.og.url,
}
}
end
POST 一発で 4 種類の URL が返る。フロント側はこの中から場面に応じて使い分ける。
まとめ
CarrierWave + MiniMagick の自動派生は、コード量にして 30 行で「画像配信の悩みごと」を 9 割消せる。残り 1 割は responsive srcset と blur placeholder(BlurHash / ThumbHash)だが、それは別記事で。
画像処理は最初の 1 日で組むと、後の数十時間が浮く。