关于 vercel-blob
vercel-blob 是使用 @vercel/blob 在 Vercel Blob 中存储文件的技能,面向构建文件上传与管理功能的开发者。内容涵盖服务端和客户端上传,以及列表、下载、删除和大文件分段上传。
主要功能
- 01服务端与客户端文件上传
- 02列表、下载与删除文件
- 03大文件分段上传
- 04公开与私有访问设置
- 05上传验证与安全建议
适用场景
- 01上传用户头像和文档
- 02管理图片库并分页列出文件
- 03通过客户端令牌接收文件
- 04上传超过 500 MB 的文件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill vercel-blob安装后在 Claude Code 中输入/vercel-blob确认已加载。
附带文件README.mdSKILL.json
详细介绍
Vercel Blob(对象存储)
状态:生产就绪
上次更新:2025-10-29
依赖项:无
最新版本:@vercel/[email protected]
快速入门(3 分钟)
1. 创建 Blob 存储
# In Vercel dashboard: Storage → Create Database → Blob
vercel env pull .env.local
创建:BLOB_READ_WRITE_TOKEN
2. 安装
npm install @vercel/blob
3. 上传文件(Server Action)
'use server';
import { put } from '@vercel/blob';
export async function uploadFile(formData: FormData) {
const file = formData.get('file') as File;
const blob = await put(file.name, file, {
access: 'public' // or 'private'
});
return blob.url; // https://xyz.public.blob.vercel-storage.com/file.jpg
}
重要:
- 直接从客户端上传时使用客户端上传令牌(不要暴露
BLOB_READ_WRITE_TOKEN) - 设置正确的
access级别(public或private) - 文件会通过 CDN 自动分发
5 步设置流程
第 1 步:创建 Blob 存储
Vercel 控制台:
- Project → Storage → Create Database → Blob
- 复制
BLOB_READ_WRITE_TOKEN
本地开发:
vercel env pull .env.local
创建 .env.local:
BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxx"
要点:
- 免费套餐:每月 100GB 带宽
- 文件大小限制:每个文件 500MB
- 自动通过 CDN 分发
- 公共文件会在全球范围内缓存
第 2 步:服务端上传
Next.js Server Action:
'use server';
import { put } from '@vercel/blob';
export async function uploadAvatar(formData: FormData) {
const file = formData.get('avatar') as File;
// Validate file
if (!file.type.startsWith('image/')) {
throw new Error('Only images allowed');
}
if (file.size > 5 * 1024 * 1024) {
throw new Error('Max file size: 5MB');
}
// Upload
const blob = await put(`avatars/${Date.now()}-${file.name}`, file, {
access: 'public',
addRandomSuffix: false
});
return {
url: blob.url,
pathname: blob.pathname
};
}
API 路由(Edge Runtime):
import { put } from '@vercel/blob';
export const runtime = 'edge';
export async function POST(request: Request) {
const formData = await request.formData();
const file = formData.get('file') as File;
const blob = await put(file.name, file, { access: 'public' });
return Response.json(blob);
}
第 3 步:客户端上传(预签名 URL)
创建上传令牌(Server Action):
'use server';
import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
export async function getUploadToken(filename: string) {
const jsonResponse = await handleUpload({
body: {
type: 'blob.generate-client-token',
payload: {
pathname: `uploads/${filename}`,
access: 'public',
onUploadCompleted: {
callbackUrl: `${process.env.NEXT_PUBLIC_URL}/api/upload-complete`
}
}
},
request: new Request('https://dummy'),
onBeforeGenerateToken: async (pathname) => {
// Optional: validate user permissions
return {
allowedContentTypes: ['image/jpeg', 'image/png', 'image/webp'],
maximumSizeInBytes: 5 * 1024 * 1024 // 5MB
};
},
onUploadCompleted: async ({ blob, tokenPayload }) => {
console.log('Upload completed:', blob.url);
}
});
return jsonResponse;
}
客户端上传:
'use client';
import { upload } from '@vercel/blob/client';
import { getUploadToken } from './actions';
export function UploadForm() {
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const form = e.currentTarget;
const file = (form.elements.namedItem('file') as HTMLInputElement).files?.[0];
if (!file) return;
const tokenResponse = await getUploadToken(file.name);
const blob = await upload(file.name, file, {
access: 'public',
handleUploadUrl: tokenResponse.url
});
console.log('Uploaded:', blob.url);
}
return (
<form onSubmit={handleSubmit}>
<input type="file" name="file" required />
<button type="submit">Upload</button>
</form>
);
}
第 4 步:列出、下载和删除
列出文件:
import { list } from '@vercel/blob';
const { blobs } = await list({
prefix: 'avatars/',
limit: 100
});
// Returns: { url, pathname, size, uploadedAt, ... }[]
分页列出:
let cursor: string | undefined;
const allBlobs = [];
do {
const { blobs, cursor: nextCursor } = await list({
prefix: 'uploads/',
cursor
});
allBlobs.push(...blobs);
cursor = nextCursor;
} while (cursor);
下载文件:
// Files are publicly accessible if access: 'public'
const url = 'https://xyz.public.blob.vercel-storage.com/file.pdf';
const response = await fetch(url);
const blob = await response.blob();
删除文件:
import { del } from '@vercel/blob';
await del('https://xyz.public.blob.vercel-storage.com/file.jpg');
// Or delete multiple
await del([url1, url2, url3]);
第 5 步:流式传输和分段上传
流式上传:
import { put } from '@vercel/blob';
import { createReadStream } from 'fs';
const stream = createReadStream('./large-file.mp4');
const blob = await put('videos/large-file.mp4', stream, {
access: 'public',
contentType: 'video/mp4'
});
分段上传(大于 500MB 的文件):
import { createMultipartUpload, uploadPart, completeMultipartUpload } from '@vercel/blob';
// 1. Start multipart upload
const upload = await createMultipartUpload('large-video.mp4', {
access: 'public'
});
// 2. Upload parts (chunks)
const partSize = 100 * 1024 * 1024; // 100MB chunks
const parts = [];
for (let i = 0; i < totalParts; i++) {
const chunk = getChunk(i, partSize);
const part = await uploadPart(chunk, {
uploadId: upload.uploadId,
partNumber: i + 1
});
parts.push(part);
}
// 3. Complete upload
const blob = await completeMultipartUpload({
uploadId: upload.uploadId,
parts
});
重要规则
务必做到
✅ 客户端上传时使用客户端上传令牌 - 切勿向客户端暴露 BLOB_READ_WRITE_TOKEN
✅ 设置正确的访问级别 - public(CDN)或 private(经过身份验证的访问)
✅ 验证文件类型和大小 - 上传前检查 MIME 类型和大小
✅ 按路径名组织文件 - 使用 avatars/、uploads/、documents/ 等目录结构
✅ 处理上传错误 - 网络故障、大小限制、令牌过期等
✅ 清理旧文件 - 删除未使用的文件以控制存储成本
✅ 显式设置 content-type - 确保浏览器正确处理文件(视频、PDF)
切勿这样做
❌ 切勿向客户端暴露 BLOB_READ_WRITE_TOKEN - 使用 handleUpload() 处理客户端上传
❌ 切勿跳过文件验证 - 上传前务必验证类型、大小和内容
❌ 切勿在未使用分段上传的情况下上传大于 500MB 的文件 - 大文件请使用分段上传
❌ 切勿使用通用文件名 - file.jpg 容易冲突,请使用 ${timestamp}-${name} 或 UUID
❌ 切勿假定上传一定成功 - 务必处理错误(网络、配额等)
❌ 切勿以未加密的方式存储敏感数据 - 如有需要,请先加密再上传
❌ 切勿忘记删除临时文件 - 旧上传文件会占用配额
已知问题预防
此技能可预防 10 个已记录的问题:
问题 #1:缺少环境变量
错误:Error: BLOB_READ_WRITE_TOKEN is not defined
来源:https://vercel.com/docs/storage/vercel-blob
原因:未在环境中设置令牌
预防措施:运行 vercel env pull .env.local,并确保 .env.local 已加入 .gitignore。
问题 #2:客户端上传令牌暴露
错误:安全漏洞,可能导致未经授权的上传
来源:https://vercel.com/docs/storage/vercel-blob/client-upload
原因:在客户端代码中直接使用 BLOB_READ_WRITE_TOKEN
预防措施:使用 handleUpload() 生成带有限制条件的客户端专用令牌。
问题 #3:超出文件大小限制
错误:Error: File size exceeds limit(500MB)
来源:https://vercel.com/docs/storage/vercel-blob/limits
原因:未使用分段上传就上传大于 500MB 的文件
预防措施:上传前验证文件大小;上传大文件时使用分段上传。
问题 #4:Content-Type 错误
错误:浏览器下载文件而不是显示文件(例如,PDF 被当作文本打开)
来源:生产环境调试
原因:未设置 contentType 选项,Blob 猜测的类型不正确
预防措施:始终设置 contentType: file.type 或明确指定 MIME 类型。
问题 #5:公共文件未缓存
错误:文件传输速度慢,出口流量费用高
来源:Vercel Blob 最佳实践
原因:对本应公开的文件使用 access: 'private'
预防措施:对可公开访问的文件使用 access: 'public'(CDN 缓存)。
问题 #6:未处理列表分页
错误:仅返回前 1000 个文件,遗漏其他文件
来源:https://vercel.com/docs/storage/vercel-blob/using-blob-sdk#list
原因:大型文件列表未使用游标迭代
预防措施:在循环中使用基于游标的分页,直到 cursor 未定义。
问题 #7:删除静默失败
错误:文件未删除,存储配额被占满
来源:https://github.com/vercel/storage/issues/150
原因:使用了错误的 URL 格式,未找到 Blob
预防措施:使用 put() 响应中的完整 Blob URL,并检查删除结果。
问题 #8:上传超时(大文件)
错误:文件 >100MB 时出现 Error: Request timeout
来源:Vercel 函数超时限制
原因:无服务器函数超时(免费套餐为 10s,专业套餐为 60s)
预防措施:对于大文件,使用客户端上传并调用 handleUpload()。
问题 #9:文件名冲突
错误:文件被覆盖,数据丢失
来源:生产环境调试
原因:多次上传使用相同文件名
预防措施:添加时间戳/UUID:`uploads/${Date.now()}-${file.name}`,或设置 addRandomSuffix: true。
问题 #10:缺少上传回调
错误:上传已完成,但应用状态未更新
来源:https://vercel.com/docs/storage/vercel-blob/client-upload#callback-after-upload
原因:未实现 onUploadCompleted 回调
预防措施:在 handleUpload() 中使用 onUploadCompleted 更新数据库/状态。
配置文件参考
package.json
{
"dependencies": {
"@vercel/blob": "^2.0.0"
}
}
.env.local
BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxxxx"
常见模式
模式 1:头像上传
'use server';
import { put, del } from '@vercel/blob';
export async function updateAvatar(userId: string, formData: FormData) {
const file = formData.get('avatar') as File;
// Validate
if (!file.type.startsWith('image/')) {
throw new Error('Only images allowed');
}
// Delete old avatar
const user = await db.query.users.findFirst({ where: eq(users.id, userId) });
if (user?.avatarUrl) {
await del(user.avatarUrl);
}
// Upload new
const blob = await put(`avatars/${userId}.jpg`, file, {
access: 'public',
contentType: file.type
});
// Update database
await db.update(users).set({ avatarUrl: blob.url }).where(eq(users.id, userId));
return blob.url;
}
模式 2:受保护的文件上传
'use server';
import { put } from '@vercel/blob';
import { auth } from '@/lib/auth';
export async function uploadDocument(formData: FormData) {
const session = await auth();
if (!session) throw new Error('Unauthorized');
const file = formData.get('document') as File;
// Upload as private
const blob = await put(`documents/${session.user.id}/${file.name}`, file, {
access: 'private' // Requires authentication to access
});
// Store in database with user reference
await db.insert(documents).values({
userId: session.user.id,
url: blob.url,
filename: file.name,
size: file.size
});
return blob;
}
模式 3:带分页的图片库
import { list } from '@vercel/blob';
export async function getGalleryImages(cursor?: string) {
const { blobs, cursor: nextCursor } = await list({
prefix: 'gallery/',
limit: 20,
cursor
});
const images = blobs.map(blob => ({
url: blob.url,
uploadedAt: blob.uploadedAt,
size: blob.size
}));
return { images, nextCursor };
}
依赖项
必需:
@vercel/blob@^2.0.0- Vercel Blob SDK
可选:
sharp@^0.33.0- 上传前处理图像zod@^3.24.0- 文件验证架构
官方文档
- Vercel Blob:https://vercel.com/docs/storage/vercel-blob
- 客户端上传:https://vercel.com/docs/storage/vercel-blob/client-upload
- SDK 参考:https://vercel.com/docs/storage/vercel-blob/using-blob-sdk
- GitHub:https://github.com/vercel/storage
软件包版本(已于 2025-10-29 验证)
{
"dependencies": {
"@vercel/blob": "^2.0.0"
}
}
生产环境示例
- 电子商务:产品图片、用户上传(50 万多个文件)
- 博客平台:特色图片、作者头像
- SaaS:文档上传、PDF 生成、CSV 导出
- 错误:0(已预防所有 10 个已知问题)
故障排除
问题:BLOB_READ_WRITE_TOKEN is not defined
解决方案:运行 vercel env pull .env.local,确保 .env.local 位于 .gitignore 中。
问题:文件大小超出限制(>500MB)
解决方案:使用 createMultipartUpload() API 进行分段上传。
问题:客户端上传因令牌错误而失败
解决方案:确保在服务端使用 handleUpload(),不要向客户端暴露读写令牌。
问题:文件未删除
解决方案:使用 put() 响应中的确切 URL,并检查 del() 返回值。
完整设置清单
- 已在 Vercel 控制面板中创建 Blob 存储
- 已设置
BLOB_READ_WRITE_TOKEN环境变量 - 已安装
@vercel/blob软件包 - 已实现文件验证(类型、大小)
- 客户端上传使用
handleUpload()(而非直接使用令牌) - 已为上传设置内容类型
- 访问级别正确(
public与private) - 已实现删除旧文件的功能
- 列表分页能够处理游标
- 已在本地和生产环境中测试文件上传、下载和删除
有疑问或遇到问题?
- 查看官方文档:https://vercel.com/docs/storage/vercel-blob
- 确认已设置环境变量
- 确保客户端上传使用客户端上传令牌
- 在 Vercel 控制面板中监控存储用量