Configure file uploads with Vercel Blob, Cloudflare R2, AWS S3, or UploadThing
NOW.TS comes with a flexible file upload system using adapters. You can easily switch between different storage providers without changing your application code.
The upload system uses an adapter pattern defined in src/lib/files/upload-file.ts:
export type UploadFileAdapter = {
uploadFile: (params: { file: File; path: string }) => Promise<
| { error: null; data: { url: string } }
| { error: Error; data: null }
>;
uploadFiles: (params: { file: File; path: string }[]) => Promise<
{ error: Error | null; data: { url: string } | null }[]
>;
};
The active adapter is imported in src/features/images/upload-image.action.ts.
Vercel Blob is the default and recommended option. It's automatically configured when deploying to Vercel.
Setup:
BLOB_READ_WRITE_TOKEN is automatically added to your environmentFor local development:
.env file:BLOB_READ_WRITE_TOKEN="vercel_blob_..."
Adapter code (already included at src/lib/files/vercel-blob-adapter.ts):
import { put } from "@vercel/blob";
import type { UploadFileAdapter } from "./upload-file";
export const fileAdapter: UploadFileAdapter = {
uploadFile: async (params) => {
try {
const blob = await put(params.file.name, params.file, {
access: "public",
});
return { error: null, data: { url: blob.url } };
} catch (error) {
Use Cloudflare R2 or AWS S3 for more control over your storage or to avoid vendor lock-in.
Setup:
pnpm add @aws-sdk/client-s3 mime-types
pnpm add -D @types/mime-types
.env:AWS_ENDPOINT="https://your-account-id.r2.cloudflarestorage.com"
AWS_ACCESS_KEY_ID="your-access-key"
AWS_SECRET_ACCESS_KEY="your-secret-key"
AWS_S3_BUCKET_NAME="your-bucket-name"
R2_URL="https://your-public-bucket-url.com"
src/lib/files/r2-adapter.ts:import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import type { UploadFileAdapter } from "./upload-file";
const s3 = new S3Client({
region: "auto",
endpoint: process.env.AWS_ENDPOINT,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
export const fileAdapter: UploadFileAdapter = {
uploadFile
src/features/images/upload-image.action.ts:// Change this:
import { fileAdapter } from "@/lib/files/vercel-blob-adapter";
// To this:
import { fileAdapter } from "@/lib/files/r2-adapter";
Video tutorials:
UploadThing is a developer-friendly file upload service with a generous free tier.
Setup:
pnpm add uploadthing
.env:UPLOADTHING_TOKEN="your-uploadthing-token"
src/lib/files/uploadthing-adapter.ts:import { UTApi } from "uploadthing/server";
import type { UploadFileAdapter } from "./upload-file";
export const utapi = new UTApi({});
export const fileAdapter: UploadFileAdapter = {
uploadFile: async (params) => {
const response = await utapi.uploadFiles([params.file]);
if (response[0].error) {
return
src/features/images/upload-image.action.ts:import { fileAdapter } from "@/lib/files/uploadthing-adapter";
To switch between adapters, simply change the import in src/features/images/upload-image.action.ts:
// Vercel Blob (default)
import { fileAdapter } from "@/lib/files/vercel-blob-adapter";
// Cloudflare R2 / AWS S3
import { fileAdapter } from "@/lib/files/r2-adapter";
// UploadThing
import { fileAdapter } from "@/lib/files/uploadthing-adapter";
By default, image upload is disabled. To enable it:
src/site-config.tsenableImageUpload to true:export const SiteConfig = {
// ...
features: {
enableImageUpload: true,
// ...
},
};
This enables drag-and-drop and click-to-upload functionality throughout the app.