Note: This documentation was written by Claude 3.5 Sonnet.
This project supports cloud-based file storage for handling file uploads and downloads.
Files are stored with public access by default, making them accessible via URL. This is useful for sharing uploaded content, displaying images, and integrating with external services.
The project supports two storage backends:
- Vercel Blob - Default for all deployments (recommended)
- S3 - Planned for AWS/S3-compatible storage
Vercel Blob is the default storage driver and works seamlessly in both local development and production environments.
# Storage driver selection (defaults to vercel-blob)
FILE_STORAGE_TYPE=vercel-blob # or s3 (coming soon)
# Optional: Subdirectory prefix for organizing files
FILE_STORAGE_PREFIX=uploads
# === Vercel Blob (FILE_STORAGE_TYPE=vercel-blob) ===
BLOB_READ_WRITE_TOKEN=<auto on Vercel>
VERCEL_BLOB_CALLBACK_URL= # Optional: For local webhook testing with ngrok
# === S3 (FILE_STORAGE_TYPE=s3, not yet implemented) ===
# FILE_STORAGE_S3_BUCKET=
# FILE_STORAGE_S3_REGION=
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=Vercel Blob works in both local development and production environments:
- Go to your Vercel project → Storage tab
- Click Connect Database → Blob → Continue
- Name it (e.g., "Files") and click Create
- Pull environment variables locally:
vercel env pullThat's it! File uploads will now work seamlessly in both development and production.
The useFileUpload hook automatically selects the optimal upload method based on your storage backend:
- Vercel Blob: Direct browser → CDN upload (fastest, default)
- S3: Presigned URL upload (when implemented)
"use client";
import { useFileUpload } from "hooks/use-presigned-upload";
function FileUploadComponent() {
const { upload, isUploading } = useFileUpload();
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const result = await upload(file);
if (!result) return; // Upload failed (error shown via toast)
// File uploaded successfully
console.log("Public URL:", result.url);
console.log("Pathname (key):", result.pathname);
};
return (
<input type="file" onChange={handleFileChange} disabled={isUploading} />
);
}sequenceDiagram
participant Browser
participant UploadURL as /api/storage/upload-url
participant Vercel as Vercel Blob CDN
Browser->>UploadURL: POST (request client token)
Note over Browser,UploadURL: User authenticated
UploadURL->>Vercel: Generate client token
Vercel-->>UploadURL: Return token
UploadURL-->>Browser: Return token + URL
Browser->>Vercel: PUT file (with token)
Vercel-->>Browser: Upload complete
Vercel->>UploadURL: Webhook: upload completed
Note over UploadURL: Optional: Save to DB
- ✅ Cloud-Based Storage: Vercel Blob provides globally distributed CDN
- ✅ Works Everywhere: Same storage in development and production
- ✅ Direct Client Upload: Browser uploads directly to CDN (fastest)
- ✅ Public Access: All files get public URLs
- ✅ Authentication: Users must be logged in to upload
- ✅ Collision Prevention: UUID-based file naming
- ✅ Type Safety: Full TypeScript support with unified interface
For server-side uploads (e.g., programmatically generated files):
import { serverFileStorage } from "lib/file-storage";
const result = await serverFileStorage.upload(buffer, {
filename: "generated-image.png",
contentType: "image/png",
});
console.log("Public URL:", result.sourceUrl);The /api/storage/upload-url endpoint handles the onUploadCompleted webhook from Vercel Blob. You can add custom logic here:
// src/app/api/storage/upload-url/route.ts
onUploadCompleted: async ({ blob, tokenPayload }) => {
const { userId } = JSON.parse(tokenPayload);
// Save to database
await db.files.create({
url: blob.url,
pathname: blob.pathname,
userId,
size: blob.size,
contentType: blob.contentType,
});
// Send notification
// await sendNotification(userId, "File uploaded!");
};To test Vercel Blob's onUploadCompleted webhook locally, use ngrok:
# Terminal 1: Start your app
pnpm dev
# Terminal 2: Start ngrok
ngrok http 3000
# Add to .env.local
VERCEL_BLOB_CALLBACK_URL=https://abc123.ngrok-free.appWithout ngrok, uploads will work but onUploadCompleted won't be called locally.
To implement a custom storage driver (e.g., Cloudflare R2, MinIO, S3):
- Create a new file in
src/lib/file-storage/(e.g.,r2-file-storage.ts) - Implement the
FileStorageinterface fromfile-storage.interface.ts - Add your driver to
index.ts - Update
FILE_STORAGE_TYPEenvironment variable
The FileStorage interface provides:
upload()- Server-side file uploadcreateUploadUrl()- Generate presigned URL for client uploads (optional)download(),delete(),exists(),getMetadata(),getSourceUrl()
| Feature | Vercel Blob | S3 (Planned) |
|---|---|---|
| Direct Client Upload | ✅ Yes | ✅ Yes (presigned) |
| CDN | ✅ Global | Configurable |
| Cost | Pay-as-you-go | Pay-as-you-go |
| Best For | All deployments | AWS ecosystem |
| Setup Complexity | Minimal | Moderate |
| Local Development | ✅ Works with token | ✅ Works |
Local filesystem storage is not supported because:
- AI APIs can't access localhost: When AI APIs receive
http://localhost:3000/file.png, they cannot fetch the file - Serverless incompatibility: Platforms like Vercel don't support persistent filesystem
- No CDN: Files aren't globally distributed
Solution: Vercel Blob provides a free tier and works seamlessly in both local development and production. Simply run vercel env pull to get your token locally.