[{"data":1,"prerenderedAt":1359},["ShallowReactive",2],{"content:\u002F2026\u002Fblob-s3-imgbed":3,"surround:\u002F2026\u002Fblob-s3-imgbed":1353},{"id":4,"title":5,"body":6,"categories":1323,"date":1325,"description":1326,"draft":1327,"extension":1328,"image":1329,"meta":1330,"navigation":1334,"path":1335,"permalink":1336,"published":1336,"readingTime":1337,"recommend":1336,"references":1336,"seo":1342,"sitemap":1343,"stem":1344,"tags":1345,"type":1350,"updated":1351,"__hash__":1352},"content\u002Fposts\u002F2026\u002Fblob-s3-imgbed.md","blob-s3-imgbed：把 Vercel Blob 改造成 S3 兼容图床",{"type":7,"value":8,"toc":1305},"minimark",[9,31,35,51,54,106,109,117,124,134,203,206,209,245,248,252,266,280,284,295,324,327,335,353,357,432,439,453,456,649,653,666,732,735,802,826,829,842,846,908,911,914,1005,1008,1032,1158,1161,1206,1209,1221,1247,1261,1267,1273,1276,1289,1299,1302],[10,11,12,16,19,22,25,28],"timeline",{},[13,14,15],"p",{},"{背景}",[13,17,18],{},"博客评论系统 Twikoo 的图片上传依赖第三方图床，近期服务稳定性无法保证。",[13,20,21],{},"{尝试}",[13,23,24],{},"考虑迁移至 Cloudflare R2，因支付方式绑定受限而搁置。",[13,26,27],{},"{结果}",[13,29,30],{},"转向 Vercel Blob，并为其构建了一层 S3 兼容网关。",[32,33,34],"h2",{"id":34},"问题的由来",[13,36,37,38,45,46,50],{},"本站评论区由 ",[39,40,44],"a",{"href":41,"rel":42},"https:\u002F\u002Ftwikoo.js.org\u002F",[43],"nofollow","Twikoo"," 驱动，其图片上传通过 ",[47,48,49],"code",{"code":49},"imgUploader"," 回调实现，存储需要自行对接图床。此前一直使用 sm.ms，近期该服务可靠性明显下降：已上传的图片在数日后出现失效，且无明确公告。免费第三方图床的可持续性本就存疑，迁移势在必行。",[13,52,53],{},"候选方案的评估结果如下：",[55,56,57,70],"table",{},[58,59,60],"thead",{},[61,62,63,67],"tr",{},[64,65,66],"th",{},"方案",[64,68,69],{},"问题",[71,72,73,82,90,98],"tbody",{},[61,74,75,79],{},[76,77,78],"td",{},"Cloudflare R2",[76,80,81],{},"需要绑定支付方式，本人验证无法通过",[61,83,84,87],{},[76,85,86],{},"直接使用 Vercel Blob SDK",[76,88,89],{},"仅支持服务端调用，浏览器端无法直接使用",[61,91,92,95],{},[76,93,94],{},"AWS S3",[76,96,97],{},"成本与账户管理开销与需求不匹配（因本人特殊原因无法使用）",[61,99,100,103],{},[76,101,102],{},"自建图床",[76,104,105],{},"运维成本过高",[13,107,108],{},"排除以上选项后，Vercel Blob 成为最合适的存储后端：开通即用，免费额度为 1GB 存储与每月 10GB 带宽，与本站评论区的图片量级完全匹配；且博客本身部署于 Vercel，存储与计算处于同一生态。",[13,110,111,112,116],{},"但它存在一个关键障碍：",[113,114,115],"strong",{},"Vercel Blob 的 API 与 S3 不兼容","——既不支持 S3 的 XML 协议，也不支持 SigV4 签名认证，官方 SDK 又只能在服务端运行。而 Twikoo 的上传行为发生在浏览器端，存储能力无法直接接入。",[13,118,119,120,123],{},"解决思路有二：编写一个专用的上传转发接口，或者将 Blob 完整封装为 S3 兼容服务。后者具备更普遍的复用价值，于是实现了本项目——它后来改名为 ",[113,121,122],{},"blob-s3-imgbed","，并迁到了独立仓库：",[125,126],"link-card",{"className":127,"description":130,"icon":131,"link":132,"title":133},[128,129],"gradient-card","active","把 Vercel Blob 包装成 S3 兼容 + 简单 HTTP 双接口的对象存储网关","https:\u002F\u002Fgithub.com\u002Ffavicon.ico","https:\u002F\u002Fgithub.com\u002FKrits03\u002Fblob-s3-imgbed","Krits03\u002Fblob-s3-imgbed",[135,136,137],"card-list",{},[138,139,140,161,175,181,187,197],"ul",{},[141,142,143,146,147,150,151,150,154,150,157,160],"li",{},[113,144,145],{},"S3 兼容接口","：支持 ",[47,148,149],{"code":149},"PUT"," \u002F ",[47,152,153],{"code":153},"GET",[47,155,156],{"code":156},"HEAD",[47,158,159],{"code":159},"DELETE","，自带 AWS SigV4 签名校验，Twikoo 的「S3 \u002F R2 \u002F MinIO」插件可直接对接",[141,162,163,166,167,170,171,174],{},[113,164,165],{},"简单 HTTP 接口","：",[47,168,169],{"code":169},"POST \u002Fapi\u002Fupload","（Bearer Token）+ ",[47,172,173],{"code":173},"GET \u002Fapi\u002Fdownload\u002F{key}","（302 重定向 \u002F 流式代理），无需任何 SDK",[141,176,177,180],{},[113,178,179],{},"内置上传页面","：访问根路径即可拖拽上传图片，一键复制绑定域名后的回调 URL",[141,182,183,186],{},[113,184,185],{},"零数据库","：Bucket 仅作逻辑前缀，对象真实存放在 Vercel Blob",[141,188,189,192,193,196],{},[113,190,191],{},"签名安全","：AccessKey \u002F SecretKey 自定义，用 ",[47,194,195],{"code":195},"timingSafeEqual"," 常量时间比较；上传 Token 同样常量时间校验",[141,198,199,202],{},[113,200,201],{},"支持 Range","：下载接口支持断点续传 \u002F 视频拖拽",[32,204,205],{"id":205},"设计与实现",[13,207,208],{},"项目在 Vercel Blob 之上构建网关层，对外同时提供两套接口，共享同一个 Blob Store 后端：",[138,210,211,232],{},[141,212,213,215,216,219,220,223,224,227,228,231],{},[113,214,145],{},"：路径形如 ",[47,217,218],{"code":218},"\u002Fs3\u002F{bucket}\u002F{key}","，支持 PUT、GET、HEAD、DELETE 四类对象操作，认证采用标准 SigV4 签名校验。任何使用 ",[47,221,222],{"code":222},"@aws-sdk\u002Fclient-s3"," 或 AWS CLI 的调用方均可零改动接入。网关本身没有 bucket 实体，",[47,225,226],{"code":226},"bucket"," 仅作为路径前缀用于逻辑分组，对象实际存储于 Blob 中 ",[47,229,230],{"code":230},"{bucket}\u002F{key}"," 对应的位置。",[141,233,234,236,237,240,241,244],{},[113,235,165],{},"：上传为携带 Bearer Token 的 ",[47,238,239],{"code":239},"fetch"," POST 请求；下载默认以 302 重定向至 Blob CDN 地址，附加 ",[47,242,243],{"code":243},"?proxy=1"," 参数则切换为流式代理，隐藏源地址并支持 Range 请求。",[13,246,247],{},"完整请求链路如下：",[249,250],"mermaid",{"code":251},"flowchart LR\n    C[\"Twikoo \u002F curl \u002F 浏览器\"]\n    C -->|\"S3 协议：@aws-sdk\u002Fclient-s3\"| S3[\"\u002Fs3\u002F{bucket}\u002F{key}\u003Cbr\u002F>SigV4 签名校验\"]\n    C -->|\"简单 HTTP：fetch\"| UP[\"POST \u002Fapi\u002Fupload\u003Cbr\u002F>Bearer Token\"]\n    C -->|\"简单 HTTP：fetch\"| DL[\"GET \u002Fapi\u002Fdownload\u002F{key}\u003Cbr\u002F>302 重定向 \u002F ?proxy=1\"]\n    S3 --> B[\"Vercel Blob\"]\n    UP --> B\n    DL --> B",[13,253,254,255,258,259,262,263,265],{},"技术栈：Next.js 15 App Router（Route Handler）+ ",[47,256,257],{"code":257},"@vercel\u002Fblob"," SDK + ",[47,260,261],{"code":261},"node:crypto","。SigV4 校验是自己写的一套 HMAC 计算，没有引入 AWS SDK；签名比较使用 ",[47,264,195],{"code":195},"，避免时序侧信道。",[267,268,270],"folding",{"title":269},"仓库目录结构",[271,272,277],"pre",{"className":273,"code":275,"language":276},[274],"language-text",".\n├── app\u002F\n│   ├── layout.tsx                      # 根布局（metadata \u002F html 骨架）\n│   ├── page.tsx                        # 网页上传页（拖拽上传 + 复制 URL）\n│   ├── s3\u002F[...key]\u002Froute.ts            # S3 兼容接口（PUT\u002FGET\u002FHEAD\u002FDELETE + SigV4）\n│   └── api\u002F\n│       ├── upload\u002Froute.ts             # 简单上传接口（raw body \u002F multipart）\n│       └── download\u002F[...key]\u002Froute.ts  # 简单下载接口（302 \u002F 流式代理）\n├── lib\u002F\n│   └── sigv4.ts                        # AWS SigV4 签名校验实现\n├── .env.example                        # 环境变量模板\n├── DEPLOY.md                           # 详细部署文档\n├── TWIKOO_S3_CONFIG.md                 # Twikoo 管理面板 S3 插件配置指南\n└── next.config.mjs\n","text",[47,278,275],{"__ignoreMap":279},"",[32,281,283],{"id":282},"部署到-vercel","部署到 Vercel",[13,285,286,287,290,291,294],{},"新版把 Next.js 应用挪到了",[113,288,289],{},"仓库根目录","，Import 仓库后 Root Directory 保持默认即可，不需要再填 ",[47,292,293],{"code":293},"vercel"," 之类的子目录，Framework Preset 会自动识别为 Next.js。",[296,297,298,311,318],"ol",{},[141,299,300,301,306,307,310],{},"打开 ",[39,302,305],{"href":303,"rel":304},"https:\u002F\u002Fvercel.com\u002Fnew",[43],"Vercel Dashboard"," → ",[113,308,309],{},"Import"," 该仓库",[141,312,313,314,317],{},"在 ",[113,315,316],{},"Settings → Environment Variables"," 里填入下面的变量",[141,319,320,321],{},"点击 ",[113,322,323],{},"Deploy",[13,325,326],{},"不想点面板也可以用 CLI：",[271,328,333],{"className":329,"code":331,"language":332,"meta":279},[330],"language-sh","npm install -g vercel   # 如未安装\nvercel                  # 首次部署（preview 环境）\nvercel --prod           # 部署到生产环境\n","sh",[47,334,331],{"__ignoreMap":279},[13,336,337,338,341,342,345,346,306,349,352],{},"本地开发则是 ",[47,339,340],{"code":340},"npm install"," → 复制 ",[47,343,344],{"code":344},".env.example"," 为 ",[47,347,348],{"code":348},".env.local",[47,350,351],{"code":351},"npm run dev","。",[354,355,356],"h3",{"id":356},"环境变量",[55,358,359,373],{},[58,360,361],{},[61,362,363,366,370],{},[64,364,365],{},"变量名",[64,367,369],{"align":368},"center","必填",[64,371,372],{},"说明",[71,374,375,388,404,416],{},[61,376,377,382,385],{},[76,378,379],{},[47,380,381],{"code":381},"BLOB_READ_WRITE_TOKEN",[76,383,384],{"align":368},"是",[76,386,387],{},"Vercel Blob 读写令牌。Blob Store 绑到同一项目时会自动注入，跨项目需手动填写",[61,389,390,395,397],{},[76,391,392],{},[47,393,394],{"code":394},"S3_ACCESS_KEY",[76,396,384],{"align":368},[76,398,399,400,403],{},"S3 签名校验用 AccessKey，自定义值（如 ",[47,401,402],{"code":402},"twikoo-blob","）",[61,405,406,411,413],{},[76,407,408],{},[47,409,410],{"code":410},"S3_SECRET_KEY",[76,412,384],{"align":368},[76,414,415],{},"S3 签名校验用 SecretKey，强随机字符串",[61,417,418,423,425],{},[76,419,420],{},[47,421,422],{"code":422},"UPLOAD_TOKEN",[76,424,384],{"align":368},[76,426,427,428,431],{},"简单上传接口（",[47,429,430],{"code":430},"\u002Fapi\u002Fupload","）的 Bearer Token，强随机字符串",[13,433,434,435],{},"密钥建议用强随机值：",[47,436,438],{"className":437,"code":438,"copy":279,"language":332},[330],"openssl rand -hex 32",[440,441,444],"alert",{"title":442,"type":443},"本地开发要手动填 BLOB_READ_WRITE_TOKEN","warning",[13,445,446,447,449,450,352],{},"Vercel 不会把 Blob 令牌注入本地环境，",[47,448,351],{"code":351}," 之前需要自己复制一份：Vercel Dashboard → Storage → Blob Store → ",[113,451,452],{},"Copy Blob Read Write Token",[32,454,455],{"id":455},"接口参考",[457,458,460,546,644],"tab",{":tabs":459},"[\"S3 兼容接口\",\"简单 HTTP 接口\",\"网页上传\"]",[461,462,463,466,537,540],"template",{"v-slot:tab1":279},[13,464,465],{},"需要 SigV4 签名，任何 S3 客户端都能零改动接入：",[55,467,468,480],{},[58,469,470],{},[61,471,472,475,478],{},[64,473,474],{},"方法",[64,476,477],{},"路径",[64,479,372],{},[71,481,482,495,511,524],{},[61,483,484,488,492],{},[76,485,486],{},[47,487,149],{"code":149},[76,489,490],{},[47,491,218],{"code":218},[76,493,494],{},"上传对象",[61,496,497,501,505],{},[76,498,499],{},[47,500,153],{"code":153},[76,502,503],{},[47,504,218],{"code":218},[76,506,507,508,403],{},"下载对象（支持 ",[47,509,510],{"code":510},"Range",[61,512,513,517,521],{},[76,514,515],{},[47,516,156],{"code":156},[76,518,519],{},[47,520,218],{"code":218},[76,522,523],{},"获取对象元信息",[61,525,526,530,534],{},[76,527,528],{},[47,529,159],{"code":159},[76,531,532],{},[47,533,218],{"code":218},[76,535,536],{},"删除对象（幂等）",[13,538,539],{},"用 AWS CLI 试一下最直观：",[271,541,544],{"className":542,"code":543,"language":332,"meta":279},[330],"aws configure   # region 填 auto，AccessKey\u002FSecretKey 用 S3_ACCESS_KEY \u002F S3_SECRET_KEY\n\naws s3 cp test.jpg --endpoint-url https:\u002F\u002F\u003C你的域名>\u002Fs3 s3:\u002F\u002Fcomments\u002Ftest.jpg\naws s3 cp --endpoint-url https:\u002F\u002F\u003C你的域名>\u002Fs3 s3:\u002F\u002Fcomments\u002Ftest.jpg downloaded.jpg\n",[47,545,543],{"__ignoreMap":279},[461,547,548,551,630,638],{"v-slot:tab2":279},[13,549,550],{},"带 Bearer Token 即可，无需任何 SDK：",[55,552,553,563],{},[58,554,555],{},[61,556,557,559,561],{},[64,558,474],{},[64,560,477],{},[64,562,372],{},[71,564,565,583,600,614],{},[61,566,567,572,577],{},[76,568,569],{},[47,570,571],{"code":571},"POST",[76,573,574],{},[47,575,576],{"code":576},"\u002Fapi\u002Fupload?name={filename}&path={prefix}",[76,578,579,580],{},"raw body 上传，需 ",[47,581,582],{"code":582},"Authorization: Bearer \u003CUPLOAD_TOKEN>",[61,584,585,589,594],{},[76,586,587],{},[47,588,571],{"code":571},[76,590,591],{},[47,592,593],{"code":593},"\u002Fapi\u002Fupload?path={prefix}",[76,595,596,597,403],{},"multipart\u002Fform-data 上传（字段 ",[47,598,599],{"code":599},"file",[61,601,602,606,611],{},[76,603,604],{},[47,605,153],{"code":153},[76,607,608],{},[47,609,610],{"code":610},"\u002Fapi\u002Fdownload\u002F{key}",[76,612,613],{},"302 重定向到 Blob CDN",[61,615,616,620,625],{},[76,617,618],{},[47,619,153],{"code":153},[76,621,622],{},[47,623,624],{"code":624},"\u002Fapi\u002Fdownload\u002F{key}?proxy=1",[76,626,627,628,403],{},"流式代理返回内容（支持 ",[47,629,510],{"code":510},[13,631,632,634,635,352],{},[47,633,430],{"code":430}," 也支持用 query 传 token：",[47,636,637],{"code":637},"?token=\u003CUPLOAD_TOKEN>",[271,639,642],{"className":640,"code":641,"language":332,"meta":279},[330],"# 上传\ncurl -X POST \"https:\u002F\u002F\u003C你的域名>\u002Fapi\u002Fupload?name=test.jpg&path=test\" \\\n  -H \"Authorization: Bearer \u003CUPLOAD_TOKEN>\" \\\n  -H \"Content-Type: image\u002Fjpeg\" \\\n  --data-binary @test.jpg\n# → { \"url\": \"...\", \"key\": \"test\u002F20260924\u002F1758...-test.jpg\", \"contentType\": \"image\u002Fjpeg\", \"size\": 10240 }\n",[47,643,641],{"__ignoreMap":279},[461,645,646],{"v-slot:tab3":279},[13,647,648],{},"直接访问站点根路径就是内置的上传页，拖拽图片即可上传，传完能一键复制绑定域名后的回调 URL —— 偶尔传几张图时不用写任何脚本。",[32,650,652],{"id":651},"接入-twikoo","接入 Twikoo",[440,654,657],{"title":655,"type":656},"推荐用管理面板的 S3 插件","info",[13,658,659,660,662,663,665],{},"Twikoo 的「S3 \u002F R2 \u002F MinIO」插件把配置存在服务端，密钥不会进入浏览器；前端 ",[47,661,49],{"code":49}," 则必须把 ",[47,664,422],{"code":422}," 写进页面，只适合自用或内部场景。",[55,667,668,680],{},[58,669,670],{},[61,671,672,675,678],{},[64,673,674],{},"方式",[64,676,677],{},"适用场景",[64,679,372],{},[71,681,682,699,721],{},[61,683,684,690,693],{},[76,685,686,689],{},[113,687,688],{},"S3 插件","（推荐）",[76,691,692],{},"管理面板可视化配置",[76,694,695,696],{},"密钥保存在 Twikoo 服务端，不暴露给浏览器，详见仓库的 ",[47,697,698],{"code":698},"TWIKOO_S3_CONFIG.md",[61,700,701,706,709],{},[76,702,703,704],{},"前端 ",[47,705,49],{"code":49},[76,707,708],{},"自定义前端上传",[76,710,711,712,714,715,717,718,720],{},"在博客页面里用 ",[47,713,239],{"code":239}," 调 ",[47,716,430],{"code":430},"，",[47,719,422],{"code":422}," 会暴露在前端",[61,722,723,726,729],{},[76,724,725],{},"网页手动上传",[76,727,728],{},"偶尔上传几张图",[76,730,731],{},"直接访问站点根路径拖拽上传",[13,733,734],{},"S3 插件里最关键的几项：",[55,736,737,747],{},[58,738,739],{},[61,740,741,744],{},[64,742,743],{},"配置项",[64,745,746],{},"值",[71,748,749,761,773,789],{},[61,750,751,756],{},[76,752,753],{},[47,754,755],{"code":755},"S3_ENDPOINT",[76,757,758],{},[47,759,760],{"code":760},"https:\u002F\u002F\u003C你的域名>\u002Fs3",[61,762,763,768],{},[76,764,765],{},[47,766,767],{"code":767},"S3_FORCE_PATH_STYLE",[76,769,770],{},[47,771,772],{"code":772},"true",[61,774,775,780],{},[76,776,777],{},[47,778,779],{"code":779},"S3_CDN_URL",[76,781,782,717,785,788],{},[47,783,784],{"code":784},"https:\u002F\u002F\u003C你的域名>\u002Fapi\u002Fdownload",[113,786,787],{},"不含 bucket","（填错会导致上传成功但图片不显示）",[61,790,791,796],{},[76,792,793],{},[47,794,795],{"code":795},"S3_REGION",[76,797,798,801],{},[47,799,800],{"code":800},"us-east-1","（任意值均可）",[267,803,805,811,820],{"title":804},"备选：前端 imgUploader 的写法",[13,806,807,808,810],{},"前端的 ",[47,809,49],{"code":49}," 回调长这样，好处是页面里不用引入 S3 SDK：",[271,812,818],{"className":813,"code":815,"filename":816,"language":817,"meta":279},[814],"language-js","twikoo.init({\n  envId: '\u003C你的 envId>',\n  imgUploader: {\n    async upload(file) {\n      const res = await fetch(\n        'https:\u002F\u002F\u003C网关域名>\u002Fapi\u002Fupload?name='\n        + encodeURIComponent(file.name) + '&path=comments',\n        {\n          method: 'POST',\n          headers: {\n            'Authorization': 'Bearer \u003CUPLOAD_TOKEN>',\n            'Content-Type': file.type,\n          },\n          body: file,\n        }\n      );\n      const data = await res.json();\n      return { url: data.url };\n    },\n  },\n});\n","twikoo.init","js",[47,819,815],{"__ignoreMap":279},[13,821,822,823,825],{},"代价是 ",[47,824,422],{"code":422}," 会出现在客户端代码里，无法回避；缓解因素是 Vercel Blob 的公开 URL 不可枚举、读取无需权限，整体风险可控。要求更高的场景应把上传逻辑移到服务端，网关仅对内开放。",[32,827,828],{"id":828},"限制与费用",[830,831,833],"quote",{"icon":832},"tabler:brand-vercel",[13,834,835,836,841],{},"以下要点整理自 ",[39,837,840],{"href":838,"rel":839},"https:\u002F\u002Fvercel.com\u002Fdocs\u002Fvercel-blob\u002Fusage-and-pricing",[43],"Vercel Blob Pricing","（官方页面更新于 2026-09-23）。Blob 按区域定价，官方也可能调整，请以文档页面为准。",[354,843,845],{"id":844},"hobby免费版额度","Hobby（免费版）额度",[55,847,848,861],{},[58,849,850],{},[61,851,852,855,858],{},[64,853,854],{},"资源",[64,856,857],{},"免费额度",[64,859,860],{},"超出后",[71,862,863,877,888,898],{},[61,864,865,868,871],{},[76,866,867],{},"Blob 存储容量",[76,869,870],{},"1 GB \u002F 月",[76,872,873,876],{},[113,874,875],{},"无法继续使用 Blob","，需等待 30 天额度重置或升级 Pro",[61,878,879,882,885],{},[76,880,881],{},"Simple Operations",[76,883,884],{},"前 10,000 次",[76,886,887],{},"同上",[61,889,890,893,896],{},[76,891,892],{},"Advanced Operations",[76,894,895],{},"前 2,000 次",[76,897,887],{},[61,899,900,903,906],{},[76,901,902],{},"Blob Data Transfer",[76,904,905],{},"前 10 GB",[76,907,887],{},[13,909,910],{},"超限不会自动扣费，只会收到提醒邮件并暂停 Blob 功能。Edge Requests、Fast Origin Transfer 按标准 CDN 费率另计，且免费额度在项目内所有 Vercel 服务间共享。",[354,912,913],{"id":913},"本项目会消耗哪些额度",[55,915,916,926],{},[58,917,918],{},[61,919,920,923],{},[64,921,922],{},"操作",[64,924,925],{},"计费项",[71,927,928,945,966,983,991],{},[61,929,930,938],{},[76,931,932,935,936],{},[47,933,934],{"code":934},"PUT \u002Fs3\u002F{bucket}\u002F{key}","、",[47,937,169],{"code":169},[76,939,940,941,944],{},"1 次 ",[113,942,943],{},"Advanced Operation","（服务端接收上传还会产生 Fast Data Transfer）",[61,946,947,956],{},[76,948,949,150,951,150,953],{},[47,950,156],{"code":156},[47,952,153],{"code":153},[47,954,955],{"code":955},"DELETE \u002Fs3\u002F...",[76,957,958,959,962,963],{},"内部调用 ",[47,960,961],{"code":961},"head()","，各计 1 次 ",[113,964,965],{},"Simple Operation",[61,967,968,977],{},[76,969,970,972,973,976],{},[47,971,159],{"code":159}," 的 ",[47,974,975],{"code":975},"del()"," 本身",[76,978,979,982],{},[113,980,981],{},"免费","，但计入速率限制",[61,984,985,988],{},[76,986,987],{},"浏览器加载 Blob 公开 URL（302 后的地址）",[76,989,990],{},"cache MISS 计 1 次 Simple Operation；无论 HIT\u002FMISS，每次访问都计 1 次 Edge Request",[61,992,993,1002],{},[76,994,995,997,998,1001],{},[47,996,243],{"code":243}," 或 ",[47,999,1000],{"code":1000},"\u002Fs3"," GET 流式代理",[76,1003,1004],{},"额外的 Functions 侧数据传输费用",[13,1006,1007],{},"省额度的三条建议：",[138,1009,1010,1019,1025],{},[141,1011,1012,1013,972,1015,1018],{},"优先走 ",[47,1014,610],{"code":610},[113,1016,1017],{},"302 重定向","：浏览器直连 Blob CDN，Blob Data Transfer 平均比 Fast Data Transfer 便宜约 3 倍。",[141,1020,1021,1022,1024],{},"避免高频 ",[47,1023,156],{"code":156}," 探测，每次都是 1 次 Simple Operation。",[141,1026,1027,352],{},[1028,1029,1031],"tip",{"tip":1030},"而不是高流量站点的 CDN 主力","把 Blob 当图片存档",[267,1033,1035,1042,1109,1112],{"title":1034},"Pro 版参考价与速率上限",[13,1036,1037,1038,1041],{},"以 ",[47,1039,1040],{"code":1040},"iad1"," 区域为例：",[55,1043,1044,1056],{},[58,1045,1046],{},[61,1047,1048,1050,1053],{},[64,1049,854],{},[64,1051,1052],{},"套餐内含",[64,1054,1055],{},"超出单价",[71,1057,1058,1069,1079,1089,1099],{},[61,1059,1060,1063,1066],{},[76,1061,1062],{},"存储容量",[76,1064,1065],{},"5 GB",[76,1067,1068],{},"$0.023 \u002F GB-month",[61,1070,1071,1073,1076],{},[76,1072,881],{},[76,1074,1075],{},"100,000 次",[76,1077,1078],{},"$0.40 \u002F 百万次",[61,1080,1081,1083,1086],{},[76,1082,892],{},[76,1084,1085],{},"10,000 次",[76,1087,1088],{},"$5.00 \u002F 百万次",[61,1090,1091,1093,1096],{},[76,1092,902],{},[76,1094,1095],{},"100 GB",[76,1097,1098],{},"$0.05 \u002F GB",[61,1100,1101,1104,1106],{},[76,1102,1103],{},"Fast Origin Transfer",[76,1105,1095],{},[76,1107,1108],{},"$0.06 \u002F GB",[13,1110,1111],{},"请求速率上限：",[55,1113,1114,1130],{},[58,1115,1116],{},[61,1117,1118,1121,1124,1127],{},[64,1119,1120],{},"限制",[64,1122,1123],{},"Hobby",[64,1125,1126],{},"Pro",[64,1128,1129],{},"Enterprise",[71,1131,1132,1145],{},[61,1133,1134,1136,1139,1142],{},[76,1135,881],{},[76,1137,1138],{},"1,200 \u002F 分钟（20\u002Fs）",[76,1140,1141],{},"7,200 \u002F 分钟（120\u002Fs）",[76,1143,1144],{},"9,000 \u002F 分钟（150\u002Fs）",[61,1146,1147,1149,1152,1155],{},[76,1148,892],{},[76,1150,1151],{},"900 \u002F 分钟（15\u002Fs）",[76,1153,1154],{},"4,500 \u002F 分钟（75\u002Fs）",[76,1156,1157],{},"7,500 \u002F 分钟（125\u002Fs）",[13,1159,1160],{},"其余几条硬限制：",[138,1162,1163,1169,1175,1181,1187,1193,1199],{},[141,1164,1165,1168],{},[113,1166,1167],{},"上传请求体上限约 4.5 MB","：上传要经 Vercel Function 中转，受 Serverless 请求体限制约束；更大文件需改用 Blob 客户端直传。",[141,1170,1171,1174],{},[113,1172,1173],{},"单 Blob 缓存上限 512 MB","：超过后永不被 CDN 缓存，每次访问都会同时产生 Simple Operation 与 Fast Origin Transfer 费用。",[141,1176,1177,1180],{},[113,1178,1179],{},"单文件最大 5 TB","，官方建议超过 100 MB 用 multipart 上传。",[141,1182,1183,1186],{},[113,1184,1185],{},"区域固定","：Blob Store 可在 19 个区域创建，创建后不可更改，各区域价格不同。",[141,1188,1189,1192],{},[113,1190,1191],{},"控制台操作也计费","：在 Vercel Dashboard 浏览文件列表、上传文件、查看详情都会计入 Operations。",[141,1194,1195,1198],{},[113,1196,1197],{},"S3 接口只覆盖对象级操作","：不支持 ListObjects 等 bucket 级 API —— 网关没有真实的 bucket 概念。",[141,1200,1201,1202,1205],{},"上传关闭了 ",[47,1203,1204],{"code":1204},"addRandomSuffix","，文件名依赖时间戳区分，理论上存在冲突可能。",[32,1207,1208],{"id":1208},"常见问题",[267,1210,1212],{"title":1211},"访问 \u002Fapi\u002Fupload 返回 401？",[13,1213,1214,1216,1217,1220],{},[47,1215,422],{"code":422}," 没配置，或请求头里的 ",[47,1218,1219],{"code":1219},"Authorization: Bearer \u003Ctoken>"," 与环境变量不一致。",[267,1222,1224],{"title":1223},"S3 客户端报 SignatureDoesNotMatch？",[13,1225,1226,1227,150,1229,1231,1232,1235,1236,1239,1240,1243,1244,352],{},"依次检查三件事：",[47,1228,394],{"code":394},[47,1230,410],{"code":410}," 是否与客户端一致；",[47,1233,1234],{"code":1234},"endpoint"," 是否为 ",[47,1237,1238],{"code":1238},"https:\u002F\u002F\u003C域名>\u002Fs3","（",[113,1241,1242],{},"不要带尾部斜杠","）；是否设置了 ",[47,1245,1246],{"code":1246},"forcePathStyle: true",[267,1248,1250],{"title":1249},"上传成功但图片不显示？",[13,1251,1252,1253,1255,1256,717,1259,352],{},"Twikoo 场景下通常是 ",[47,1254,779],{"code":779}," 留空或填错，应填 ",[47,1257,1258],{"code":1258},"https:\u002F\u002F\u003C域名>\u002Fapi\u002Fdownload",[113,1260,787],{},[267,1262,1264],{"title":1263},"能上传多大的文件？",[13,1265,1266],{},"约 4.5 MB —— 这是 Vercel Serverless Function 的请求体上限。更大的文件建议直接用 Vercel Blob 客户端的直传 API。",[267,1268,1270],{"title":1269},"免费额度用完了会怎样？",[13,1271,1272],{},"Hobby 版含 1 GB 存储、1 万次 Simple Operations、2 千次 Advanced Operations、10 GB 流量；超限只会暂停 Blob 功能（不扣费），需等 30 天额度重置或升级 Pro。",[32,1274,1275],{"id":1275},"结语",[13,1277,1278,1279,1282,1283,1285,1286,1288],{},"这次改动比上次发文时更多：项目从 ",[47,1280,1281],{"code":1281},"vercel-blob-to-s3"," 改名为 ",[113,1284,122],{}," 并迁到独立仓库，Next.js 应用从子目录上移到仓库根目录，Twikoo 的接入方式也从「前端 ",[47,1287,49],{"code":49},"」换成了推荐的「管理面板 S3 插件」——密钥终于不用写进页面里了。原先的 Cloudflare Workers 版本没有跟着迁过来，新仓库里只有 Vercel 实现。",[13,1290,1291,1292,1295,1296,1298],{},"仓库里现在有两份文档：",[47,1293,1294],{"code":1294},"DEPLOY.md"," 是从零部署到 Vercel 的完整步骤，",[47,1297,698],{"code":698}," 是 S3 插件的字段说明与排错表，比这篇文章更细。如有类似需求——不限于 Twikoo，任何需要在浏览器端使用 Vercel Blob 的场景——欢迎使用或提交 issue。",[13,1300,1301],{},"本站评论图床已切换，欢迎在评论区验证。",[13,1303,1304],{},"这应该是开学前的最后一篇了awa.\n之后我会出一篇详细部署文章，敬请期待.",{"title":279,"searchDepth":1306,"depth":1306,"links":1307},4,[1308,1310,1311,1315,1316,1317,1321,1322],{"id":34,"depth":1309,"text":34},2,{"id":205,"depth":1309,"text":205},{"id":282,"depth":1309,"text":283,"children":1312},[1313],{"id":356,"depth":1314,"text":356},3,{"id":455,"depth":1309,"text":455},{"id":651,"depth":1309,"text":652},{"id":828,"depth":1309,"text":828,"children":1318},[1319,1320],{"id":844,"depth":1314,"text":845},{"id":913,"depth":1314,"text":913},{"id":1208,"depth":1309,"text":1208},{"id":1275,"depth":1309,"text":1275},[1324],"技术","2026-08-30 20:11:00","把 Vercel Blob 包装成 S3 兼容 + 简单 HTTP 双接口的对象存储网关，接入 Twikoo 评论图床",false,"md","https:\u002F\u002Fvercel-blob.api.kr033.top\u002Fapi\u002Fdownload\u002Fsite\u002Fpost\u002Fvercel-blob-to-s3\u002F20260830\u002F1788127625440-1000004294.jpg",{"aside":1331,"slots":1333},[1332],"toc",{},true,"\u002F2026\u002Fblob-s3-imgbed",null,{"text":1338,"minutes":1339,"time":1340,"words":1341},"14 min read",13.845,830700,2769,{"title":5,"description":1326},{"loc":1335},"posts\u002F2026\u002Fblob-s3-imgbed",[44,1346,1347,1348,1349],"Vercel","S3","图床","Blob","tech","2026-09-24 22:40:00","kPqMNKiZDeao5FnwkKQ-i1fEprgrTDh1TzdgTXGZrKg",[1336,1354],{"title":1355,"path":1356,"stem":1357,"date":1358,"type":1350,"children":-1},"站点无限期维护通知","\u002F2026\u002Fsite-maintenance-notice","posts\u002F2026\u002Fsite-maintenance-notice","2026-08-30 20:21:21",1790328637447]