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