vercel-blob
Integrate Vercel Blob object storage for file uploads, image management, and CDN-delivered assets in Next.js applications. Supports client-side uploads with presigned URLs and multipart transfers. Use when implementing file uploads (images, PDFs, videos), managing user-generated content, or troubleshooting missing tokens, size limit errors, or client upload failures.
What this skill does
# Vercel Blob (Object Storage) **Status**: Production Ready **Last Updated**: 2025-10-29 **Dependencies**: None **Latest Versions**: `@vercel/[email protected]` --- ## Quick Start (3 Minutes) ### 1. Create Blob Store ```bash # In Vercel dashboard: Storage → Create Database → Blob vercel env pull .env.local ``` Creates: `BLOB_READ_WRITE_TOKEN` ### 2. Install ```bash npm install @vercel/blob ``` ### 3. Upload File (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 `access` level (`public` vs `private`) - Files are automatically distributed via CDN --- ## The 5-Step Setup Process ### Step 1: Create Blob Store **Vercel Dashboard**: 1. Project → Storage → Create Database → Blob 2. Copy `BLOB_READ_WRITE_TOKEN` **Local Development**: ```bash vercel env pull .env.local ``` Creates `.env.local`: ```bash 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 --- ### Step 2: Server-Side Upload **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); } ``` --- ### Step 3: Client-Side Upload (Presigned URLs) **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> ); } ``` --- ### Step 4: List, Download, Delete **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]); ``` --- ### Step 5: Streaming & Multipart **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 }); ``` --- ## Critical Rules ### Always Do ✅ **Use client upload tokens for client-side uploads** - Never expose `BLOB_READ_WRITE_TOKEN` to client ✅ **Set correct access level** - `public` (CDN) or `private` (authenticated access) ✅ **Validate file types and sizes** - Before upload, check MIME type and size ✅ **Use pathname organization** - `avatars/`, `uploads/`, `documents/` for structure ✅ **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) ### Never Do ❌ **Never expose `BLOB_READ_WRITE_TOKEN` to client** - Use `handleUpload()` for client uploads ❌ **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** - `file.jpg` collides, use `${timestamp}-${name}` or UUID ❌ **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 --- ## Known Issues Prevention This skill prevents **10 documented issues**: ### Issue #1: Missing Environment Variable **Error**: `Error: BLOB_READ_WRITE_TOKEN is not defined` **Source**: https://vercel.com/docs/storage/vercel-blob **Why It Happens**: Token not set in environment **Prevention**: Run `vercel env pull .env.local` and ensure `.env.local` in `.gitignore`. ### Issue #2: Client Upload Token Exposed **Error**: Security vulnerability, unauthorized uploads **Source**: https://vercel.com/docs/storage/vercel-blob/client-upload **Why It Happens**: Using `BLOB_READ_WRITE_TOKEN` dire
Related in Image & Video
watch
IncludedWatch a video (URL or local path). Downloads with yt-dlp, extracts auto-scaled frames with ffmpeg, pulls the transcript from captions (or Whisper API fallback), and hands the result to Claude so it can answer questions about what's in the video.
physical-ai-defect-image-generation
IncludedUse when the user wants to orchestrate defect image generation, run associated setup, or handle outputs on OSMO. The Day 0 path handles cold-start with USD-to-ROI, image-edit augmentation, and AnomalyGen to create initial PCBA datasets. The Day 1 path performs inference and labeling on real images. This skill helps with first-time asset setup, creation of finetuning checkpoints, and configuring deployment. Trigger keywords: defect image generation, dig workflow, dig pipeline, defect image detection workflow, aoi pipeline, aoi anomalygen, usd2roi anomalygen, day 0 pcba, day 1 pcba, day 1 real-photo alignment, day 1 manual roi, metal surface anomaly, glass defect, anomalygen finetune, setup_pcb, setup_metal, setup_glass, setup_pretrained, dig setup, dig datasets, dig pretrained checkpoint, dig image-edit endpoint.
accelint-react-best-practices
IncludedReact performance optimization and best practices. ALWAYS use this skill when working with any React code - writing components, hooks, JSX; refactoring; optimizing re-renders, memoization, state management; reviewing for performance; fixing hydration mismatches; debugging infinite re-renders, stale closures, input focus loss, animations restarting; preventing remounting; implementing transitions, lazy initialization, effect dependencies. Even simple React tasks benefit from these patterns. Covers React 19+ (useEffectEvent, Activity, ref props). Triggers - useEffect, useState, useMemo, useCallback, memo, inline components, nested components, components inside components, re-render, performance, hydration, SSR, Next.js, useDeferredValue, combined hooks.
elevenlabs-agents
IncludedBuild conversational AI voice agents with ElevenLabs Platform using React, JavaScript, React Native, or Swift SDKs. Configure agents, tools (client/server/MCP), RAG knowledge bases, multi-voice, and Scribe real-time STT. Use when: building voice chat interfaces, implementing AI phone agents with Twilio, configuring agent workflows or tools, adding RAG knowledge bases, testing with CLI "agents as code", or troubleshooting deprecated @11labs packages, Android audio cutoff, CSP violations, dynamic variables, or WebRTC config. Keywords: ElevenLabs Agents, ElevenLabs voice agents, AI voice agents, conversational AI, @elevenlabs/react, @elevenlabs/client, @elevenlabs/react-native, @elevenlabs/elevenlabs-js, @elevenlabs/agents-cli, elevenlabs SDK, voice AI, TTS, text-to-speech, ASR, speech recognition, turn-taking model, WebRTC voice, WebSocket voice, ElevenLabs conversation, agent system prompt, agent tools, agent knowledge base, RAG voice agents, multi-voice agents, pronunciation dictionary, voice speed control, elevenlabs scribe, @11labs deprecated, Android audio cutoff, CSP violation elevenlabs, dynamic variables elevenlabs, case-sensitive tool names, webhook authentication
humanizer
IncludedHumanize AI-generated text by detecting and removing patterns typical of LLM output. Rewrites text to sound natural, specific, and human. Uses 28 pattern detectors, 560+ AI vocabulary terms across 3 tiers, and statistical analysis (burstiness, type-token ratio, readability) for comprehensive detection. Use when asked to humanize text, de-AI writing, make content sound more natural/human, review writing for AI patterns, score text for AI detection, or improve AI-generated drafts. Covers content, language, style, communication, and filler categories.
generating-mermaid-diagrams
IncludedSalesforce architecture diagrams using Mermaid with ASCII fallback. Use this skill when generating text-based diagrams for Salesforce architecture, OAuth flows, ERDs, integration sequences, or Agentforce structure. TRIGGER when: user says "diagram", "visualize", "ERD", or asks for sequence diagrams, flowcharts, class diagrams, or architecture visualizations in Mermaid. DO NOT TRIGGER when: user wants PNG/SVG image output (use generating-visual-diagrams), or asks about non-Salesforce systems.