vercel-blob
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVercel Blob (Object Storage)
Vercel Blob(对象存储)
Status: Production Ready
Last Updated: 2025-10-29
Dependencies: None
Latest Versions:
@vercel/blob@2.0.0状态:已就绪可用于生产环境
最后更新:2025-10-29
依赖:无
最新版本:
@vercel/blob@2.0.0Quick Start (3 Minutes)
快速开始(3分钟)
1. Create Blob Store
1. 创建Blob存储
bash
undefinedbash
undefinedIn Vercel dashboard: Storage → Create Database → Blob
在Vercel控制台中:存储 → 创建数据库 → Blob
vercel env pull .env.local
Creates: `BLOB_READ_WRITE_TOKEN`vercel env pull .env.local
生成:`BLOB_READ_WRITE_TOKEN`2. Install
2. 安装
bash
npm install @vercel/blobbash
npm install @vercel/blob3. Upload File (Server Action)
3. 上传文件(Server Action)
typescript
'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
}CRITICAL:
- Use client upload tokens for direct client uploads (don't expose )
BLOB_READ_WRITE_TOKEN - Set correct level (
accessvspublic)private - Files are automatically distributed via CDN
typescript
'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' // 或 'private'
});
return blob.url; // https://xyz.public.blob.vercel-storage.com/file.jpg
}重要提示:
- 对于直接客户端上传,请使用客户端上传令牌(不要暴露)
BLOB_READ_WRITE_TOKEN - 设置正确的级别(
access或public)private - 文件会自动通过CDN分发
The 5-Step Setup Process
五步设置流程
Step 1: Create Blob Store
步骤1:创建Blob存储
Vercel Dashboard:
- Project → Storage → Create Database → Blob
- Copy
BLOB_READ_WRITE_TOKEN
Local Development:
bash
vercel env pull .env.localCreates :
.env.localbash
BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxx"Key Points:
- Free tier: 100GB bandwidth/month
- File size limit: 500MB per file
- Automatic CDN distribution
- Public files are cached globally
Vercel控制台:
- 项目 → 存储 → 创建数据库 → Blob
- 复制
BLOB_READ_WRITE_TOKEN
本地开发:
bash
vercel env pull .env.local生成:
.env.localbash
BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxx"关键点:
- 免费层:每月100GB带宽
- 文件大小限制:单文件500MB
- 自动CDN分发
- 公开文件全球缓存
Step 2: Server-Side Upload
步骤2:服务端上传
Next.js Server Action:
typescript
'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 Route (Edge Runtime):
typescript
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);
}Next.js Server Action:
typescript
'use server';
import { put } from '@vercel/blob';
export async function uploadAvatar(formData: FormData) {
const file = formData.get('avatar') as File;
// 验证文件
if (!file.type.startsWith('image/')) {
throw new Error('仅支持图片');
}
if (file.size > 5 * 1024 * 1024) {
throw new Error('最大文件大小:5MB');
}
// 上传
const blob = await put(`avatars/${Date.now()}-${file.name}`, file, {
access: 'public',
addRandomSuffix: false
});
return {
url: blob.url,
pathname: blob.pathname
};
}API路由(Edge Runtime):
typescript
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);
}Step 3: Client-Side Upload (Presigned URLs)
步骤3:客户端上传(预签名URL)
Create Upload Token (Server Action):
typescript
'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;
}Client Upload:
typescript
'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>
);
}创建上传令牌(Server Action):
typescript
'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) => {
// 可选:验证用户权限
return {
allowedContentTypes: ['image/jpeg', 'image/png', 'image/webp'],
maximumSizeInBytes: 5 * 1024 * 1024 // 5MB
};
},
onUploadCompleted: async ({ blob, tokenPayload }) => {
console.log('上传完成:', blob.url);
}
});
return jsonResponse;
}客户端上传:
typescript
'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('上传完成:', blob.url);
}
return (
<form onSubmit={handleSubmit}>
<input type="file" name="file" required />
<button type="submit">上传</button>
</form>
);
}Step 4: List, Download, Delete
步骤4:列出、下载、删除文件
List Files:
typescript
import { list } from '@vercel/blob';
const { blobs } = await list({
prefix: 'avatars/',
limit: 100
});
// Returns: { url, pathname, size, uploadedAt, ... }[]List with Pagination:
typescript
let cursor: string | undefined;
const allBlobs = [];
do {
const { blobs, cursor: nextCursor } = await list({
prefix: 'uploads/',
cursor
});
allBlobs.push(...blobs);
cursor = nextCursor;
} while (cursor);Download File:
typescript
// 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();Delete File:
typescript
import { del } from '@vercel/blob';
await del('https://xyz.public.blob.vercel-storage.com/file.jpg');
// Or delete multiple
await del([url1, url2, url3]);列出文件:
typescript
import { list } from '@vercel/blob';
const { blobs } = await list({
prefix: 'avatars/',
limit: 100
});
// 返回:{ url, pathname, size, uploadedAt, ... }[]分页列出文件:
typescript
let cursor: string | undefined;
const allBlobs = [];
do {
const { blobs, cursor: nextCursor } = await list({
prefix: 'uploads/',
cursor
});
allBlobs.push(...blobs);
cursor = nextCursor;
} while (cursor);下载文件:
typescript
// 如果设置了access: 'public',文件可公开访问
const url = 'https://xyz.public.blob.vercel-storage.com/file.pdf';
const response = await fetch(url);
const blob = await response.blob();删除文件:
typescript
import { del } from '@vercel/blob';
await del('https://xyz.public.blob.vercel-storage.com/file.jpg');
// 或批量删除
await del([url1, url2, url3]);Step 5: Streaming & Multipart
步骤5:流式上传与分块上传
Streaming Upload:
typescript
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'
});Multipart Upload (Large Files >500MB):
typescript
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
});流式上传:
typescript
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):
typescript
import { createMultipartUpload, uploadPart, completeMultipartUpload } from '@vercel/blob';
// 1. 启动分块上传
const upload = await createMultipartUpload('large-video.mp4', {
access: 'public'
});
// 2. 上传分块(片段)
const partSize = 100 * 1024 * 1024; // 100MB分块
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. 完成分块上传
const blob = await completeMultipartUpload({
uploadId: upload.uploadId,
parts
});Critical Rules
重要规则
Always Do
必须遵循
✅ Use client upload tokens for client-side uploads - Never expose to client
BLOB_READ_WRITE_TOKEN✅ Set correct access level - (CDN) or (authenticated access)
publicprivate✅ Validate file types and sizes - Before upload, check MIME type and size
✅ Use pathname organization - , , for structure
avatars/uploads/documents/✅ Handle upload errors - Network failures, size limits, token expiration
✅ Clean up old files - Delete unused files to manage storage costs
✅ Set content-type explicitly - For correct browser handling (videos, PDFs)
✅ 客户端上传使用客户端上传令牌 - 切勿向客户端暴露
BLOB_READ_WRITE_TOKEN✅ 设置正确的访问级别 - (CDN分发)或(需认证访问)
publicprivate✅ 验证文件类型和大小 - 上传前检查MIME类型和大小
✅ 使用路径名组织文件 - 用、、来结构化存储
avatars/uploads/documents/✅ 处理上传错误 - 网络故障、大小限制、令牌过期等情况
✅ 清理旧文件 - 删除未使用的文件以控制存储成本
✅ 显式设置content-type - 确保浏览器正确处理文件(视频、PDF等)
Never Do
切勿操作
❌ Never expose to client - Use for client uploads
BLOB_READ_WRITE_TOKENhandleUpload()❌ Never skip file validation - Always validate type, size, content before upload
❌ Never upload files >500MB without multipart - Use multipart upload for large files
❌ Never use generic filenames - collides, use or UUID
file.jpg${timestamp}-${name}❌ Never assume uploads succeed - Always handle errors (network, quota, etc.)
❌ Never store sensitive data unencrypted - Encrypt before upload if needed
❌ Never forget to delete temporary files - Old uploads consume quota
❌ 切勿向客户端暴露 - 客户端上传请使用
BLOB_READ_WRITE_TOKENhandleUpload()❌ 切勿跳过文件验证 - 上传前务必验证类型、大小、内容
❌ 切勿上传>500MB的文件而不使用分块上传 - 大文件请使用分块上传
❌ 切勿使用通用文件名 - 会导致覆盖,使用或UUID
file.jpg${timestamp}-${name}❌ 切勿假设上传一定会成功 - 务必处理错误(网络、配额等)
❌ 切勿存储未加密的敏感数据 - 如有需要,请在上传前加密
❌ 切勿忘记删除临时文件 - 旧上传文件会占用配额
Known Issues Prevention
已知问题预防
This skill prevents 10 documented issues:
本指南可预防10个已记录的问题:
Issue #1: Missing Environment Variable
问题1:环境变量缺失
Error:
Source: https://vercel.com/docs/storage/vercel-blob
Why It Happens: Token not set in environment
Prevention: Run and ensure in .
Error: BLOB_READ_WRITE_TOKEN is not definedvercel env pull .env.local.env.local.gitignore错误:
来源:https://vercel.com/docs/storage/vercel-blob
原因:未在环境中设置令牌
解决方法:运行,并确保已加入。
Error: BLOB_READ_WRITE_TOKEN is not definedvercel env pull .env.local.env.local.gitignoreIssue #2: Client Upload Token Exposed
问题2:客户端上传令牌暴露
Error: Security vulnerability, unauthorized uploads
Source: https://vercel.com/docs/storage/vercel-blob/client-upload
Why It Happens: Using directly in client code
Prevention: Use to generate client-specific tokens with constraints.
BLOB_READ_WRITE_TOKENhandleUpload()错误:安全漏洞,未授权上传
来源:https://vercel.com/docs/storage/vercel-blob/client-upload
原因:在客户端代码中直接使用
解决方法:使用生成带约束的客户端专用令牌。
BLOB_READ_WRITE_TOKENhandleUpload()Issue #3: File Size Limit Exceeded
问题3:文件大小超出限制
Error: (500MB)
Source: https://vercel.com/docs/storage/vercel-blob/limits
Why It Happens: Uploading file >500MB without multipart upload
Prevention: Validate file size before upload, use multipart upload for large files.
Error: File size exceeds limit错误:(500MB)
来源:https://vercel.com/docs/storage/vercel-blob/limits
原因:上传>500MB的文件但未使用分块上传
解决方法:上传前验证文件大小,大文件使用分块上传。
Error: File size exceeds limitIssue #4: Wrong Content-Type
问题4:Content-Type错误
Error: Browser downloads file instead of displaying (e.g., PDF opens as text)
Source: Production debugging
Why It Happens: Not setting option, Blob guesses incorrectly
Prevention: Always set or explicit MIME type.
contentTypecontentType: file.type错误:浏览器下载文件而非直接显示(如PDF以文本形式打开)
来源:生产环境调试
原因:未设置选项,Blob自动猜测错误
解决方法:始终设置或显式MIME类型。
contentTypecontentType: file.typeIssue #5: Public File Not Cached
问题5:公开文件未被缓存
Error: Slow file delivery, high egress costs
Source: Vercel Blob best practices
Why It Happens: Using for files that should be public
Prevention: Use for publicly accessible files (CDN caching).
access: 'private'access: 'public'错误:文件分发缓慢,出口流量成本高
来源:Vercel Blob最佳实践
原因:应为公开的文件设置了
解决方法:公开访问的文件使用(CDN缓存)。
access: 'private'access: 'public'Issue #6: List Pagination Not Handled
问题6:未处理列表分页
Error: Only first 1000 files returned, missing files
Source: https://vercel.com/docs/storage/vercel-blob/using-blob-sdk#list
Why It Happens: Not iterating with cursor for large file lists
Prevention: Use cursor-based pagination in loop until is undefined.
cursor错误:仅返回前1000个文件,丢失部分文件
来源:https://vercel.com/docs/storage/vercel-blob/using-blob-sdk#list
原因:未使用游标遍历大型文件列表
解决方法:在循环中使用基于游标的分页,直到为undefined。
cursorIssue #7: Delete Fails Silently
问题7:删除操作静默失败
Error: Files not deleted, storage quota fills up
Source: https://github.com/vercel/storage/issues/150
Why It Happens: Using wrong URL format, blob not found
Prevention: Use full blob URL from response, check deletion result.
put()错误:文件未被删除,存储配额被占满
来源:https://github.com/vercel/storage/issues/150
原因:使用错误的URL格式,Blob未找到
解决方法:使用返回的完整Blob URL,检查删除操作结果。
put()Issue #8: Upload Timeout (Large Files)
问题8:大文件上传超时
Error: for files >100MB
Source: Vercel function timeout limits
Why It Happens: Serverless function timeout (10s free tier, 60s pro)
Prevention: Use client-side upload with for large files.
Error: Request timeouthandleUpload()错误:(文件>100MB)
来源:Vercel函数超时限制
原因:无服务器函数超时(免费层10秒,专业层60秒)
解决方法:大文件使用进行客户端上传。
Error: Request timeouthandleUpload()Issue #9: Filename Collisions
问题9:文件名冲突
Error: Files overwritten, data loss
Source: Production debugging
Why It Happens: Using same filename for multiple uploads
Prevention: Add timestamp/UUID: or .
`uploads/${Date.now()}-${file.name}`addRandomSuffix: true错误:文件被覆盖,数据丢失
来源:生产环境调试
原因:多个上传使用相同文件名
解决方法:添加时间戳/UUID: 或设置。
`uploads/${Date.now()}-${file.name}`addRandomSuffix: trueIssue #10: Missing Upload Callback
问题10:缺少上传回调
Error: Upload completes but app state not updated
Source: https://vercel.com/docs/storage/vercel-blob/client-upload#callback-after-upload
Why It Happens: Not implementing callback
Prevention: Use in to update database/state.
onUploadCompletedonUploadCompletedhandleUpload()错误:上传完成但应用状态未更新
来源:https://vercel.com/docs/storage/vercel-blob/client-upload#callback-after-upload
原因:未实现回调
解决方法:在中使用来更新数据库/状态。
onUploadCompletedhandleUpload()onUploadCompletedConfiguration Files Reference
配置文件参考
package.json
package.json
json
{
"dependencies": {
"@vercel/blob": "^2.0.0"
}
}json
{
"dependencies": {
"@vercel/blob": "^2.0.0"
}
}.env.local
.env.local
bash
BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxxxx"bash
BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxxxx"Common Patterns
常见模式
Pattern 1: Avatar Upload
模式1:头像上传
typescript
'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;
}typescript
'use server';
import { put, del } from '@vercel/blob';
export async function updateAvatar(userId: string, formData: FormData) {
const file = formData.get('avatar') as File;
// 验证
if (!file.type.startsWith('image/')) {
throw new Error('仅支持图片');
}
// 删除旧头像
const user = await db.query.users.findFirst({ where: eq(users.id, userId) });
if (user?.avatarUrl) {
await del(user.avatarUrl);
}
// 上传新头像
const blob = await put(`avatars/${userId}.jpg`, file, {
access: 'public',
contentType: file.type
});
// 更新数据库
await db.update(users).set({ avatarUrl: blob.url }).where(eq(users.id, userId));
return blob.url;
}Pattern 2: Protected File Upload
模式2:受保护的文件上传
typescript
'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;
}typescript
'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('未授权');
const file = formData.get('document') as File;
// 上传为私有文件
const blob = await put(`documents/${session.user.id}/${file.name}`, file, {
access: 'private' // 需要认证才能访问
});
// 在数据库中存储并关联用户
await db.insert(documents).values({
userId: session.user.id,
url: blob.url,
filename: file.name,
size: file.size
});
return blob;
}Pattern 3: Image Gallery with Pagination
模式3:带分页的图片画廊
typescript
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 };
}typescript
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 };
}Dependencies
依赖项
Required:
- - Vercel Blob SDK
@vercel/blob@^2.0.0
Optional:
- - Image processing before upload
sharp@^0.33.0 - - File validation schemas
zod@^3.24.0
必需:
- - Vercel Blob SDK
@vercel/blob@^2.0.0
可选:
- - 上传前图片处理
sharp@^0.33.0 - - 文件验证 schema
zod@^3.24.0
Official Documentation
官方文档
- Vercel Blob: https://vercel.com/docs/storage/vercel-blob
- Client Upload: https://vercel.com/docs/storage/vercel-blob/client-upload
- SDK Reference: https://vercel.com/docs/storage/vercel-blob/using-blob-sdk
- GitHub: https://github.com/vercel/storage
Package Versions (Verified 2025-10-29)
包版本(2025-10-29已验证)
json
{
"dependencies": {
"@vercel/blob": "^2.0.0"
}
}json
{
"dependencies": {
"@vercel/blob": "^2.0.0"
}
}Production Example
生产环境示例
- E-commerce: Product images, user uploads (500K+ files)
- Blog Platform: Featured images, author avatars
- SaaS: Document uploads, PDF generation, CSV exports
- Errors: 0 (all 10 known issues prevented)
- 电商:商品图片、用户上传(50万+文件)
- 博客平台:特色图片、作者头像
- SaaS:文档上传、PDF生成、CSV导出
- 错误率:0(所有10个已知问题均已预防)
Troubleshooting
故障排查
Problem: BLOB_READ_WRITE_TOKEN is not defined
BLOB_READ_WRITE_TOKEN is not defined问题:BLOB_READ_WRITE_TOKEN is not defined
BLOB_READ_WRITE_TOKEN is not definedSolution: Run , ensure in .
vercel env pull .env.local.env.local.gitignore解决方法:运行,确保已加入。
vercel env pull .env.local.env.local.gitignoreProblem: File size exceeded (>500MB)
问题:文件大小超出限制(>500MB)
Solution: Use multipart upload with API.
createMultipartUpload()解决方法:使用 API进行分块上传。
createMultipartUpload()Problem: Client upload fails with token error
问题:客户端上传因令牌错误失败
Solution: Ensure using server-side, don't expose read/write token to client.
handleUpload()解决方法:确保服务端使用,不要向客户端暴露读写令牌。
handleUpload()Problem: Files not deleting
问题:文件无法删除
Solution: Use exact URL from response, check return value.
put()del()解决方法:使用返回的精确URL,检查的返回值。
put()del()Complete Setup Checklist
完整设置检查清单
- Blob store created in Vercel dashboard
- environment variable set
BLOB_READ_WRITE_TOKEN - package installed
@vercel/blob - File validation implemented (type, size)
- Client upload uses (not direct token)
handleUpload() - Content-type set for uploads
- Access level correct (vs
public)private - Deletion of old files implemented
- List pagination handles cursor
- Tested file upload/download/delete locally and in production
Questions? Issues?
- Check official docs: https://vercel.com/docs/storage/vercel-blob
- Verify environment variables are set
- Ensure using client upload tokens for client-side uploads
- Monitor storage usage in Vercel dashboard
- 已在Vercel控制台创建Blob存储
- 已设置环境变量
BLOB_READ_WRITE_TOKEN - 已安装包
@vercel/blob - 已实现文件验证(类型、大小)
- 客户端上传使用(而非直接令牌)
handleUpload() - 上传时已设置content-type
- 访问级别设置正确(vs
public)private - 已实现旧文件删除逻辑
- 文件列表已处理游标分页
- 已在本地和生产环境测试文件上传/下载/删除
有疑问?遇到问题?
- 查看官方文档:https://vercel.com/docs/storage/vercel-blob
- 验证环境变量是否已设置
- 确保客户端上传使用(而非直接使用令牌)
handleUpload() - 在Vercel控制台监控存储使用情况