Claude-skills vercel-blob

Vercel Blob object storage with CDN for Next.js. Use for file uploads (images, PDFs, videos), presigned URLs, user-generated content, file management, or encountering BLOB_READ_WRITE_TOKEN errors, file size limits, client upload token errors.

install
source · Clone the upstream repo
git clone https://github.com/secondsky/claude-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/secondsky/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/vercel-blob/skills/vercel-blob" ~/.claude/skills/secondsky-claude-skills-vercel-blob && rm -rf "$T"
manifest: plugins/vercel-blob/skills/vercel-blob/SKILL.md
source content

Vercel Blob (Object Storage)

Status: Production Ready Last Updated: 2025-12-14 Dependencies: None Latest Versions:

@vercel/blob@2.0.0


Quick Start (3 Minutes)

1. Create & Configure

# In Vercel dashboard: Storage → Create Database → Blob
vercel env pull .env.local

Creates:

BLOB_READ_WRITE_TOKEN

2. Install

bun add @vercel/blob

3. Upload File (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',
    contentType: file.type
  });

  return blob.url;
}

4. Basic Operations

import { put, del, list } from '@vercel/blob';

// Upload
const blob = await put('path/file.jpg', file, { access: 'public' });

// Delete
await del(blob.url);

// List with pagination
const { blobs, cursor } = await list({ prefix: 'uploads/', limit: 100 });

Critical Rules

Always Do

RuleWhy
Use client upload tokens for client-side uploadsNever expose
BLOB_READ_WRITE_TOKEN
to client
Set
contentType
explicitly
Correct browser handling for PDFs, videos
Use
access: 'public'
for CDN caching
Private files bypass CDN
Validate file type and size before uploadPrevent invalid uploads
Use pathname organization
avatars/
,
uploads/
,
documents/
Delete old files when replacingManage storage costs

Never Do

RuleWhy
Expose
BLOB_READ_WRITE_TOKEN
to client
Security vulnerability
Upload files >500MB without multipartUse
createMultipartUpload
API
Use generic filenamesUse
${Date.now()}-${name}
or
addRandomSuffix: true
Skip file validationAlways validate type/size
Store sensitive data unencryptedEncrypt before upload
Forget to handle pagination
list()
returns max 1000 files

Known Issues Prevention

This skill prevents 10 documented issues:

#ErrorQuick Fix
1
BLOB_READ_WRITE_TOKEN not defined
Run
vercel env pull .env.local
2Client token exposedUse
handleUpload()
for client uploads
3File size exceeded (500MB)Use multipart upload API
4Wrong content-typeSet
contentType: file.type
5No CDN cachingUse
access: 'public'
6Missing files in listUse cursor pagination
7Delete fails silentlyUse exact URL from
put()
8Upload timeoutUse client-side upload for large files
9Filename collisionsAdd timestamp or
addRandomSuffix: true
10State not updatedUse
onUploadCompleted
callback

See:

references/known-issues.md
for complete solutions with code examples.


Common Patterns Summary

PatternUse CaseKey API
Avatar UploadUser profile images
put
,
del
Protected UploadPrivate documents
put
with
access: 'private'
Image GalleryList & paginate
list
with cursor
Client UploadLarge files, progress
upload
,
handleUpload
Multipart UploadFiles >500MB
createMultipartUpload
,
uploadPart
Batch OperationsMultiple files
Promise.all
,
del([...])
Image ProcessingOptimize before upload
sharp
+
put

See:

references/common-patterns.md
for complete implementations.


Client-Side Upload (Essential Pattern)

Server Action (Token Generation):

'use server';

import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';

export async function generateUploadToken(body: HandleUploadBody) {
  return await handleUpload({
    body,
    request: new Request('https://dummy'),
    onBeforeGenerateToken: async (pathname) => ({
      allowedContentTypes: ['image/jpeg', 'image/png', 'image/webp'],
      maximumSizeInBytes: 5 * 1024 * 1024
    }),
    onUploadCompleted: async ({ blob }) => {
      // Save to database
      await db.insert(uploads).values({ url: blob.url, pathname: blob.pathname });
    }
  });
}

Client Component:

'use client';

import { upload } from '@vercel/blob/client';

const blob = await upload(file.name, file, {
  access: 'public',
  handleUploadUrl: '/api/upload'
});

Configuration

.env.local

# Created by: vercel env pull .env.local
BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxxxx"

.gitignore

.env.local
.env*.local

When to Load References

ReferenceLoad When...
references/known-issues.md
Debugging upload errors, token issues, or CDN caching problems
references/common-patterns.md
Implementing avatar uploads, galleries, client uploads, or multipart uploads

Dependencies

{
  "dependencies": {
    "@vercel/blob": "^2.0.0"
  }
}

Free Tier Limits: 100GB bandwidth/month, 500MB max file size


Official Documentation


Troubleshooting

ProblemSolution
BLOB_READ_WRITE_TOKEN not defined
Run
vercel env pull .env.local
File size exceeded (>500MB)Use multipart upload API
Client upload failsUse
handleUpload()
server-side
Files not deletingUse exact URL from
put()
response

Token Savings: ~60% (patterns extracted to references) Error Prevention: 100% (all 10 documented issues) Ready for production!