用 Cloudflare Images 當作圖片儲存、轉檔方案
# 開發筆記在網頁上放一張圖片,大概是前端最簡單的一件事:
<img src="YOUR_IMAGE_URL" />
一行就搞定。簡單到會讓人忘記這行 src 後面其實藏了很多細節。
圖片是網頁裡最重的東西。文字才幾 KB,一張沒處理過的照片動輒好幾 MB。使用者覺得快不快,很多時候不是卡在你的 JavaScript,而是卡在圖片載入的那幾秒。
要省位元組,第一步是壓縮。JPEG 用離散餘弦轉換把人眼不敏感的高頻細節丟掉,換來檔案大幅縮小,雖然是有損壓縮,但視覺上幾乎看不出差別。
近年的 WebP、AVIF 又把壓縮率往前推了一大截,代價是有些舊瀏覽器不支援,所以要用 <picture> 準備 fallback:
<picture>
<source srcset="hero.avif" type="image/avif" />
<source srcset="hero.webp" type="image/webp" />
<img src="hero.jpg" />
</picture>
瀏覽器會由上往下挑第一個支援的格式來渲染。width/height、srcset、sizes、art direction——這一塊我在另一篇文章寫得比較完整,這裡不重複。
這篇想講的是它的另一半:這些不同格式、不同尺寸的圖片,到底是從哪裡來的?
依照使用情景來說,為了在網頁上有更好的體驗,通常會對圖片做以下處理:
- 縮成列表用的 thumbnail、內頁用的中圖、大圖
- 每種尺寸再各自生一份 AVIF、WebP、JPEG
- 依裝置的螢幕寬度跟 DPR 決定實際送哪一張,或是在前端動態選擇
- 把這些檔案放進 storage,前面再接一層 CDN 快取
如果是固定數量的靜態檔案,我們大可以在 build time 時期事先將圖片編譯成各種需要的格式,但是在 UGC 的平台上並沒辦法控制數量,都是由使用者上傳後在伺服器端處理。
這些處理每一步拆開都不難,難的是做完整還要撐得住流量,這兩件事湊在一起,成本會高得不成比例。問題的核心是:圖片轉檔是 CPU-bound 的工作。
以 AVIF 舉例好了,AVIF 是從 AV1 影片編碼的關鍵影格衍生出來的格式1,而影片編碼天生就是「編碼很貴、解碼很便宜」,它刻意把 CPU 成本壓在編碼端,好讓播放時解碼順暢。
AVIF 檔案很小、瀏覽器解碼很快,而且品質相對好很多,但把一張圖壓成 AVIF 非常吃 CPU。Jake Archibald 實測過,用 libavif 最高的 effort 參數,光壓一張圖就可能花上十分鐘1。你當然不會真的開到那麼高,但這個數字說明了它的量級——AVIF 編碼比 JPEG 貴上好幾倍,是規格設計使然。
於是「在自己伺服器上動態轉圖」這個看起來最直覺的做法就變得危險。圖片一多、又剛好撞上流量尖峰,這些轉檔工作會直接把 CPU 吃滿,排擠掉其他商業邏輯的程式碼,甚至還更容易出現 OOM。
像 Next.js 的 next/image,預設就是用伺服器資源做 on-demand 轉檔,背後跑的正是 Sharp。方便是真的方便,但它等於把一個吃 CPU、又對外開放的端點塞進了你的應用程式裡。
官方文件自己都要你設好 qualities、remotePatterns 白名單,否則會變成被人拿來大量轉圖、灌爆記憶體的破口2。我個人不喜歡框架在這一層插手,多了一層我不好掌控的複雜度。
**面對高流量系統,任何 CPU-bound 且需要吃掉大量記憶體的任務,都要非常小心。**一旦流量變大,伺服器很容易就遇到瓶頸,甚至影響到其他業務邏輯的程式碼。
那麼透過 Lambda 或是非同步的方式將圖片處理 offload 到別的地方呢?可以,但這樣一來你要處理的複雜度就會指數上升。
AWS 官方自己包了一套方案(以前叫 Serverless Image Handler,現在改名 Dynamic Image Transformation for Amazon CloudFront)3,光看它的組成就知道水有多深:CloudFront 當快取層、API Gateway 當入口、Lambda 跑 Sharp 做轉檔、S3 放原圖跟 log,想要智慧裁切還得接 Amazon Rekognition,想擋盜連還得用 Secrets Manager 做簽章。這裡面每一個都是要設定、要維護、要付錢的東西。
還有一個容易被忽略的成本:頻寬。圖片是流量大戶,而雲端的對外傳輸(egress)是要另外收錢的。
直接從 S3 對外送圖又特別貴,標準做法是前面墊一層 CloudFront,靠快取把打到 origin 的請求擋下來。但這裡藏了一個陷阱:你切出來的衍生檔案越多(尺寸 × 格式),快取就越分散,一旦沒命中,就得回頭讀 S3、再轉一次、再送一次——等於同一份流量的錢付了兩遍。你為了省頻寬做的多格式多尺寸,反過來稀釋了快取的效益。
所以這件事的難處,不在任何單一步驟,而在於它是一條需要長期維護、又會隨流量放大成本的產線。除非公司規模已經可以負荷開發成本,且有足夠商業戰略意義,否則這種東西最好的策略就是不要自己做。
Cloudflare Images 幫你做完了
只要有圖片處理的需求,我現在基本上一律用 Cloudflare Images。前面講的那些事情,它都幫你搞定了。其他類似的服務還有 BunnyCDN、ImageKit 等等。
原圖只要傳一份上去。要不同尺寸、不同格式,不用預先切好幾十個檔案,而是在網址上帶參數,由 Cloudflare 在邊緣節點即時轉出來。
打開 flexible variants 之後,網址長這樣4:
https://imagedelivery.net/<account_hash>/<image_id>/w=400,quality=80
格式協商是自動的。走它的交付網址時,Cloudflare 會讀瀏覽器送來的 Accept header,支援 AVIF 就給 AVIF、只支援 WebP 就給 WebP,都不支援才 fallback 回原格式5——你不用自己寫 <picture> 去列格式,也不用自己判斷。
舉例來說,如果你打開瀏覽器複製這張圖的網址連結(https://image.kalan.dev/b856ee69-6431-48ed-7f22-3311e7d01600/normal),並觀察 Network,會發現回傳的格式是 AVIF,或是 webp,根據你的瀏覽器支援而定,但其實這張圖從頭到尾就只有一張原圖,我完全沒有做任何轉檔。
常見的操作也都內建:縮放、裁切(含依人臉裁切)、模糊、鏡像、旋轉、調亮度對比。
當然也可以掛自己的 domain。我的圖都放在 image.kalan.dev,只要那個 domain 是掛在同一個 Cloudflare 帳號底下的 zone 就行6。
CDN 快取是預設的。轉好的圖直接進 Cloudflare 的邊緣快取,同一組(原圖 + 參數)第二次以後就從最近的節點回,不會再打到 origin。
它其實有兩種用法:一種是把圖直接存在 Cloudflare(Hosted Images),另一種是圖留在你自己的 storage(R2、S3 都行),只借用它邊緣的轉檔能力(現在叫 Transformations)。
收費也對應這兩種。存在它那邊會多算儲存跟交付的量,只借轉檔就只算轉檔次數,而且有免費額度7。目前收費如下:
| Metric | Pricing |
|---|---|
| Images Transformed | First 5,000 unique transformations included + $0.50 / 1,000 unique transformations / month |
| Images Stored | $5 / 100,000 images stored / month |
| Images Delivered | $1 / 100,000 images delivered / month |
另外我也有寫了簡單的 cli,方便在 local 上傳圖片到 cloudflare images,如果有需要的話可以參考。
Footnotes
-
AVIF has landed — Jake Archibald。AVIF 衍生自 AV1 的關鍵影格;文中實測 libavif 最高 effort 壓一張圖可能超過十分鐘。 ↩ ↩2
-
next/image 官方文件,關於 on-demand 最佳化、記憶體上限與
qualities/remotePatterns白名單。 ↩ -
Dynamic Image Transformation for Amazon CloudFront(前身為 Serverless Image Handler)。 ↩
-
Cloudflare Images pricing。實際免費額度與計價以官方頁面為準。 ↩
相關文章
- 別再用 AWS Access Key 了Access Key 是 AWS 上容易被忽略的安全風險。用 OIDC 搭配 IAM Role,讓 GitHub Actions 不需要任何 secret 就能安全操作 AWS 資源
- 資料庫主鍵:AUTO_INCREMENT、UUID 與 UUIDv7後端開發常需要決定主鍵,要用 auto increment 還是 UUID?碰撞怎麼辦?UUIDv7 跟 created_at + index 的效能差多少?實際跑了 2000 萬筆資料與設計決策告訴你
- Zeabur 使用心得分享一般獨立開發者要部署服務時都會選擇 Vercel 之類的平台,但有時候需要更進階的需求如資料庫連接時,Vercel 就沒那麼方便,而一般雲端服務商的價格對獨立開發來說也很貴,這篇文章分享了一些使用 Zeabur 的心得,推薦給大家!
- 鍵盤入坑指南 - 韌體篇本篇為 IT 2023 鐵人賽文章:鍵盤入坑指南 - 韌體篇