EXIF Gallery Nuxt 開源相簿自架,照片先在瀏覽器壓縮好再上傳

EXIF Gallery Nuxt 是可免費部署在 Cloudflare 的開源個人相簿。本文實測本機部署與上傳流程:照片在瀏覽器壓縮成無 EXIF 的 WebP 才上傳,AI 只送縮圖走自己的金鑰,但隱藏照片的 API 未上鎖,自架前該知道的隱私邊界都在這裡。

用 AI 摘要這篇文章:

EXIF Gallery Nuxt 是一套可以自己架的開源相簿:照片丟進管理介面,瀏覽器先在本地壓縮成 WebP 與縮圖、讀出相機資訊,再交給架在自己 Cloudflare 帳號裡的資料庫與物件儲存。GitHub 上 126 顆星、MIT 授權,倉庫附英文與簡體中文雙語說明文件,官方示範站放了 689 張照片給你看成品。我把它抓下來在本機完整跑過一輪,也逐檔讀過伺服器與瀏覽器端的原始碼,結論先講:想把作品集或生活照放上自己網址的人,它值得裝;但它的「隱藏」功能沒有上鎖,拿來當私人相簿的開關會漏照片,隱私這件事要自己把最後一道門補上。

EXIF Gallery Nuxt 的 GitHub 倉庫頁面截圖Pin
EXIF Gallery Nuxt 的 GitHub 倉庫:MIT 授權、126 顆星,簡介寫明可免費部署在 Cloudflare 的個人相簿(圖片來源:官方倉庫)。

先說結論:放作品可以,藏照片不行

這套相簿的隱私設計有一個很反直覺的分布。大多數人擔心的兩件事它做得比想像好:上傳出去的照片檔案本身乾淨到沒有任何拍攝資料,AI 功能也只把你的一張小縮圖送去你自己設定的模型,金鑰自始至終不離開瀏覽器。真正該擔心的反而是看不出來的那層:照片資料列了「隱藏」欄位,但公開 API 根本不看這個欄位,直接打介面端點,隱藏照片連同座標欄位一起奉上。

所以給誰用?攝影作品集、公開照片牆、想順便練 Cloudflare 部署的人,裝了不後悔,官方示範站就是這種用法。手機生活照、有位置敏感性的照片、任何你只想給特定人看的內容,要嘛整套放在登入牆後面(它沒有這種機制),要嘛自己加一層防護,細節後面會講。

半小時就能在本機跑起來,先不用 Cloudflare 帳號

實測從零開始:clone 倉庫、pnpm install 裝了 1,334 個套件(Node 26.7.0),pnpm dev 直接啟動。不需要先辦 Cloudflare 帳號,開發模式會在本機模擬 D1 資料庫與 R2 物件儲存,四個資料庫遷移檔自動套用,起 log 乾淨俐落。這點對想先試用的人很友善,介面長什麼樣、上傳流程順不順,全部可以在本機驗完再決定要不要部署。

管理介面用單一共用密碼登入,沒有帳號體系。我實測密碼打錯回 401,打對拿到工作階段;兩次嘗試之間有 5 秒節流。這種設計對一人管理的相簿夠用,多人共管就超出它的設計範圍了。這裡有個文件與程式對不上的地方值得記下:README 的環境變數表寫管理密碼預設是 admin,但原始碼的行為是沒設這個變數就直接回 500 錯誤,不存在預設密碼。照文件以為可以偷懶不設的人,部署後第一件事就是踩到這個錯。

實際上傳一張照片的體驗是這套工具最順手的部分。我在本機丟了一張 86.0 kB、內含相機型號與 GPS 座標的測試 JPEG,檔案丟進去的瞬間,頁面卡片上同步列出結果:原始 86.0 kB,瀏覽器產出的 WebP 是 10.6 kB,縮圖 2.2 kB,畫面上同時把 EXIF 解析出來顯示成「相機型號、ƒ 值、ISO、焦距、拍攝時間」。這一整條壓縮與解析流程都發生在上傳之前、發生在你的瀏覽器裡。

EXIF Gallery Nuxt 上傳介面的壓縮結果截圖Pin
本機部署的上傳介面:一張 86.0 kB 的測試照片丟進去,瀏覽器當場產出 10.6 kB 的 WebP 與 2.2 kB 縮圖,相機型號與拍攝資訊同時解析完成(圖片來源:本機部署實測)。

上傳的檔案乾淨得出奇:EXIF 在瀏覽器就被洗掉

它的壓縮不是把原檔送出去再處理,順序剛好相反。原始碼裡的流程是瀏覽器先把照片解碼成像素資料,再由 Web Worker 用 JSQuash 這組編碼器重新編成 WebP、JPEG 或 AVIF,縮圖則固定壓到短邊 240 或 320 像素等級。解碼再編碼的過程會把 EXIF 區塊整個丟掉,這是編碼器的天性,而這套工具把這個特性用得很徹底。

我把經過它瀏覽器管線儲存下來的副本抓回來逐一檢查:尺寸維持 1600×1067 沒變,檔案裡找不到 EXIF、找不到 XMP、連相機字串都沒有。官方示範站的照片也一樣,隨機抓一張 5,925×3,955、1.06 MB 的 WebP 下來,中繼資料同樣全空。也就是說,放在站上的照片檔案本身不會告訴任何人你用什麼相機、在哪個時間點按的快門。

相機資訊去哪了?它被瀏覽器解析後拆成結構化欄位,存進資料庫,前台再組合顯示。你在示範站照片旁邊看到的 NIKON Z 5、鏡頭型號、焦距、光圈那些資訊,全部來自資料庫欄位而非照片檔案。這個設計讓「展示拍攝資訊」與「檔案攜帶拍攝資訊」脫鉤,前者是讀者要的,後者是隱私風險,它只保留了前者。對照會把原始檔連同中繼資料整包收走的雲端壓縮服務,這裡的取捨方向明確:伺服器只收處理過的成品。如果你原本就用瀏覽器端的圖片壓縮工具處理要上傳的圖,這套相簿把同樣的事情內建成上傳流程的一部分。

EXIF Gallery Nuxt 官方示範站的清單檢視截圖Pin
官方示範站的清單檢視:每張照片旁完整顯示相機、鏡頭、焦距、光圈、快門、ISO 與拍攝時間,拍攝資訊來自資料庫欄位而非照片檔案(圖片來源:官方示範站)。

壓縮格式預設開 WebP 與縮圖,JPEG 和 AVIF 要自己勾,另有自動縮放選項預設開啟:短邊達到 2,880 像素的照片會先縮到短邊 2,160 再編碼。示範站上存在 5,925 像素寬的照片,表示站長自己把這個選項關了,要留多大解析度是使用者自己決定的。

前台逛起來像一份攝影集,檢索能力是齊的

相簿成品好不好看,直接去官方示範站看最準。站上 689 張照片,站長用 NIKON Z 5 搭 24-200mm 旅遊鏡拍的城市與風景照,照片標題與標籤多是 AI 產生的整齊短句。前台有兩種檢視:清單式的一圖一則,每張照片旁邊完整列出相機、鏡頭、焦距、光圈、快門、ISO 與拍攝時間;切到格線檢視就是一整面照片牆,右側掛著標籤清單可以點選過濾。頁面切換有視圖轉場動畫,照片卡片還有一個 3D 傾斜效果,覺得花俏的話有環境變數可以預設關掉。

檢索這一層比多數自架相簿認真。搜尋支援多個關鍵字(用空格分隔、全部要命中),搜尋範圍涵蓋標題、描述、AI 生成的語意描述、地點、相機型號與標籤;相機與鏡頭各自能當篩選條件,等於把 EXIF 資料庫當成了檢索索引在用。我在示範站上實際驗證:輸入 NIKON 搜出 461 張,用相機型號篩選出 444 張,兩種找法都即時回應。照片排序以拍攝時間為主,可升冪降冪,示範站最老的一筆拍攝日期回到 2005 年。這些功能的意義在於:AI 產生的文字描述與結構化的 EXIF 欄位,最後都會變成讀者找照片的入口,上傳時花的那些工不會白費。

還個貼心的安排:示範站連管理介面的展示版都開放參觀,不需要密碼就能走進去摸上傳介面的長相。我確認過它只是空殼展示,實際送出上傳時 API 仍然回 401 拒絕,沒有登入就是傳不上去。想先看介面再決定要不要本機跑一輪的人,這是一條更短的路。

AI 幫你寫標題,用的是你自己的金鑰和一張縮圖

「AI 圖像分析」這幾個字容易讓人以為照片會被送去某個遠端服務,原始碼給的答案保守得多。AI 功能預設關閉,要用的話在設定頁填自己的 API 金鑰,支援 OpenAI 相容端點(意思是你能指向任何相容服務)與 Gemini 兩種。金鑰存在管理員瀏覽器的 localStorage,模型呼叫發生在瀏覽器裡,伺服器只負責收 AI 生成的文字結果。整條鏈路上,你的金鑰不經過相簿伺服器,站方(也就是自架的你)也沒有任何內建的金鑰或端點。

送出去給模型看的也不是原圖,是那張 240 等級像素的小縮圖,檔案大小在 KB 級別;唯一的例外是把縮圖格式關掉,那時它會退回送原檔,所以想壓低外送量就保留縮圖這個預設格式。中文設定下它請模型產出八個字內的標題、二十五個字內的描述與最多三個標籤,示範站上那些整齊的照片標題與標籤就是這樣來的。要留意兩件事:一是介面語言只有英文與簡體中文兩種,中文提示詞走的是簡體,生成出來的標題也是簡體字,繁體使用者要有心理準備;二是 AI 生成的品質我沒有實測,不想花自己的額度去驗證這件事,只能確認機制本身如文件所述,接上金鑰就會在選照片時自動跑。不想用 AI 的人可以完全不理它,關閉狀態下這套相簿沒有任何 AI 流量。

「隱藏」不是鎖:三個不上鎖的門

現在講該小心的地方,這部分我在本機部署上完整重現過。上傳時照片有個「隱藏」選項,前台首頁也確實不顯示隱藏照片,看起來像個私人開關。但「不顯示」只發生在前端查詢自己帶了過濾參數的時候,伺服器端的三個門都沒上鎖。

  • 照片列表 API 不帶參數去問,回傳所有照片,包括隱藏的。我在本機上傳了一張標記隱藏、帶台北座標的測試照片,無參數查詢完整回傳了它,latitude 與 longitude 欄位都在。
  • 單張照片 API 用照片 id 去問,一樣不看隱藏狀態,不需要登入就能拿到完整資料。
  • 照片檔案本身按檔名直接存取,同樣不需要登入。

官方示範站抽樣了 200 張,沒有半張隱藏照片、沒有任何座標資料,站長顯然知道這個邊界,示範站的用法是全部公開。但自架的人如果把「隱藏」當成私人相簿功能,那就是把希望放在別人想不到檔名上,而列表 API 又會把檔名告訴所有人。真要放不公開的照片,正確做法是整個站放在存取控制後面(例如 Cloudflare Access),或只把想公開的照片放進來。

順帶把寫入側的邊界也交代完:上傳、編輯、刪除三個操作都有檢查工作階段,沒登入就是 401,這部分是上鎖的。門戶大開的只有讀取側,而讀取側恰恰是隱私洩漏會發生的地方,所以整體圖像是「管得住別人亂動你的照片,管不住別人看你的照片」。自架前想清楚你的相簿要對誰開放,答案就決定了這套工具適不適合你。

GPS 自動匯入其實是壞的,這反而幫了隱私一把

照片的 GPS 座標是隱私討論裡最敏感的一項,這套工具在這裡有個有趣的意外。上傳表單有緯度、經度、地點名稱三個欄位,EXIF 解析的程式碼看起來也想自動帶入照片裡的座標,但我用一張有 GPS 的照片實測,座標欄就是空的。回頭對原始碼與解析函式的輸出,原因很直白:解析函式回傳的鍵名是小寫的 latitude 與 longitude,程式碼讀的卻是大寫的 Latitude 與 Location,永遠對不上,這行程式碼從上線到現在應該沒有成功匯入過任何一張照片的座標。

換句話說,除非你在表單手動填座標,GPS 不會進資料庫,前面擔心的「隱藏照片連座標一起漏」在現況下只威脅到手動填寫的人。這是 bug,效果上卻是隱私保護。要提醒的是反向風險:哪天作者把大小寫修好了,這個保護就消失,屆時帶 GPS 的照片上傳,座標會自動進到那個人人可查的公開 API。習慣讓手機記錄位置的人,部署這套相簿前最好檢查一下自己的照片是否內建座標,或者上傳時確認座標欄是空的。想在檔案層檢查照片攜帶哪些中繼資料,可以參考我們介紹過的EXIF 與 AI 簽章檢視工具

自架成本與該知道的邊界

部署的正式路線是 fork 倉庫、在自己帳號建 D1 資料庫與 R2 儲存桶、連結 Git 自動建置,全部落在 Cloudflare 免費層。相簿這種流量規模離免費額度上限很遠,硬體成本趨近於零,這是它相對自架 NAS 相簿方案最明顯的優勢:不用養一台機器。資料落地要說清楚:照片與資料庫都在你自己的 Cloudflare 帳號裡,但運作在別人的基礎設施上,帳號被停用或服務條款變動都會直接影響你的相簿,這是所有 serverless 自架方案共同的性質,自己權衡。效能面倒是有一個寫在設定檔裡的亮點:照片檔案的回應標頭設了快取一年且不可變,同一張照片的重複請求都由邊緣節點吸收,源站壓力極小。

兩個環境變數是必填(管理密碼與工作階段金鑰),前面提過密碼沒設會直接 500。

幾個部署前該知道的細節。倉庫裡的 wrangler 設定檔內含作者自己的 D1 資料庫 ID,fork 之後要換成自己的,照 README 走會替換,漏了這步會連到別人的庫。資料庫遷移不會在建置時自動執行,專案附了 GitHub Actions 工作流程處理,secrets 設好即可。專案本身是 2025 年 2 月開始的單人專案,185 個 commit 全部出自同一位作者,主分支最後一筆停在 2026 年 7 月,沒有發布過版本標籤,更新方式就是跟著 main 分支走。單人維護加無版本標籤,代表穩定性靠的是程式碼夠簡單而非測試與發布流程,它的伺服器端確實只有七個介面端點加一條圖片路由,結構小到可以整個讀完,這既是優點(你能自己審)也是上限(停更了你要接手)。

想試的話,就從本機開始

投入順序建議反著來:先本機跑一輪再考慮部署。clone 倉庫、corepack enable pnpm、pnpm install、pnpm dev,四個指令後打開本機網址,設好兩個環境變數就能完整操作上傳、壓縮、標籤、隱藏的全部流程,這一輪不用任何帳號。試完覺得介面與流程順手,再照官方文件把資源開在自己帳號裡上線;public 之後記得把「隱藏當鎖」的念頭收起來,只放你願意公開的照片,它就是一套輕巧、檔案乾淨、AI 邊界誠實的自架相簿。

Sliven 褚崇名
Sliven 褚崇名

每日分享科技新知、免費資源以及 WordPress、虛擬主機相關主題,任何問題歡迎在科技月球下方留言,或是發送 Email 至 [email protected] 與我聯繫。

文章: 1427

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


Share to...