ファイルサイズを 98% 削減!Sharp と SvelteKit で実装した「オンデマンド WebP 変換 API」

重い画像は嫌いだ、でも手作業はもっと嫌いだ
「このサイト、ちょっと画像重くない?」
自分のブログを見ていてそう思っちゃったんです。Lighthouse のスコアを気にする以前に、体感として引っかかる感じ。 原因は明白で、Unsplash からダウンロードした高画質な JPG をそのまま使っていたから。
「WebP に変換して、サイズも縮小すればいいじゃん」
そう、正論です。でもね、めんどくさいんですよ。 記事を書くたびに画像編集ソフトを開いて、リサイズして、WebP で書き出して……なんて作業、単純作業アレルギーの僕には耐えられない。
だからまたやっちゃいました。「人間がやるべきじゃないことは、機械にやらせよう」の精神で。
SvelteKit で「オンデマンド画像変換」を作る
今回実装したのは、「画像のURLにアクセスした瞬間に、サーバー側で勝手に最適化してくれる」 という仕組みです。
具体的にはこんな流れです。
- ブログ記事では
/images/blog/hoge.webpというパスで画像を指定する。 - サーバーはその画像がリクエストされたとき、元画像の JPG を探す。
- その場で WebP に変換 しつつ、適切なサイズにリサイズ する。
- 変換した画像をブラウザに返す(そしてサーバーに保存しておく)。
- 2回目以降のアクセスでは、保存しておいた キャッシュ画像を即座に返す。
これ、すごくないですか?
僕はただ /api/... と書くだけでいいんです。事前の変換作業は一切不要。
「必要になったタイミングで、必要な形に加工する」。これがオンデマンドの気持ちよさです。
技術的な中身(ざっくりと)
SvelteKit はフルスタックフレームワークなので、API エンドポイント(サーバー側の処理)も簡単に書けちゃいます。
今回は Node.js の fs (ファイルシステム) モジュールでファイルを読み書きし、画像処理には Sharp という超高速なライブラリを使いました。
Sharp は本当に優秀で、リサイズもフォーマット変換も一瞬です。しかも画質を維持したまま容量をガッツリ削ってくれます。
驚愕のダイエット結果
論より証拠。実際に変換された画像のサイズを見てください。自分でも引くレベルで小さくなりました。
| 画像 | 元サイズ (JPG) | 変換後 (WebP) | 削減率 |
|---|---|---|---|
| aron-visuals… | 4.2MB | 49KB | 98.8% |
| christopher-gower… | 1.1MB | 72KB | 93.5% |
| domenico-loia… | 2.7MB | 69KB | 97.4% |
| hal-gatewood… | 2.1MB | 62KB | 97.0% |
4.2MB が 49KB ですよ!? もはや誤差みたいなサイズになってしまいました。 これだけ軽くなれば、ページの表示速度が爆速になるのも当たり前です。
なぜ WebP? なぜ 1344px?
実装にあたって、いくつか迷ったポイントがありました。
WebP vs AVIF
次世代フォーマットといえば AVIF も有名ですよね。最初は AVIF にしようかと思いました。 でも、Sharp の標準設定で出力してみたら、意外にも WebP のほうがファイルサイズが小さかった んです。
もちろん、圧縮率の設定を細かくチューニングすれば AVIF のほうが小さくなる可能性は高いです。でも、その「最適な設定を探す作業」すらも面倒くさい。 「デフォルト設定でいい結果が出たほうを採用する」。この割り切りこそが、長く運用を続けるコツだと思ってます(言い訳)。
なぜ「幅 1344px」なのか
リサイズする幅を 1344px に設定したのにも、一応まじめな理由があります。
このブログの記事本文の最大幅は max-w-2xl、つまり 672px です。
Retina ディスプレイなどの高解像度スクリーンできれいに表示するには、表示サイズの 2倍 の解像度が必要と言われています。
672px × 2 = 1344px
というわけで、1344px。これ以上大きくしてもファイルサイズが無駄に増えるだけだし、これ以下だと高解像度モニタでぼやける可能性がある。 まさに「必要十分」なサイズというわけです。
こだわったポイント
1. サーバー負荷とレスポンス速度のバランス
「毎回変換してたら遅くなるんじゃない?」と思われるかもしれません。 そこはちゃんと考えてます。変換処理が走るのは 「最初のひとりがアクセスしたときだけ」 です。 変換した画像はファイルとして保存されるので、2人目以降(あるいは自分の2回目のアクセス)は、ただの静的ファイルへのアクセスと同じ爆速レスポンスになります。
いわば、「最初の一回だけちょっと頑張る」システムです。これならサーバー代を気にしているTURSOの記事のときのようなケチケチ精神(失礼、最適化精神)とも合致します。
2. 「元の画像」はそのまま残す
元の高画質 JPG はそのまま static フォルダに残しています。
もし将来、「やっぱりもっと高解像度で表示したい!」とか「WebP 以外の次世代フォーマットに対応したい!」となっても、元データさえあればいつでも生成し直せるからです。
不可逆な変換をして元データを捨ててしまうと、後戻りできませんからね。
まとめ
これで、画像のパフォーマンス問題から解放されました。 記事を書くときは、Unsplash からダウンロードしたファイルをポンと置いて、パスを書くだけ。あとはサーバーが勝手に軽量・爆速な画像を配信してくれます。
「面倒くさい」というネガティブな感情は、ときとして「システムで解決してやる!」という強力な開発エネルギーになりますね。 みなさんも、日々の「めんどくさい」を技術でねじ伏せてみてはいかがでしょうか?
さて、次は何を自動化しようかなー。