← Back to articles

How to Handle File Uploads in Next.js (2026)

File uploads are deceptively complex — size limits, security, storage, CDN delivery. Here's the complete guide for Next.js in 2026.

The Options

SolutionBest ForCost
UploadThingFastest setup, small filesFree (2GB), $10/mo (100GB)
Vercel BlobVercel users, simple storageFree (256MB), $0.15/GB
Cloudflare R2Cheapest at scale, zero egressFree (10GB), $0.015/GB
Supabase StorageSupabase usersFree (1GB), included in plan
AWS S3Enterprise, maximum control$0.023/GB + egress

Option 1: UploadThing (Easiest)

npm install uploadthing @uploadthing/react

Server Setup

// app/api/uploadthing/core.ts
import { createUploadthing } from 'uploadthing/next'

const f = createUploadthing()

export const ourFileRouter = {
  imageUploader: f({ image: { maxFileSize: '4MB', maxFileCount: 4 } })
    .middleware(async ({ req }) => {
      const user = await auth()
      if (!user) throw new Error('Unauthorized')
      return { userId: user.id }
    })
    .onUploadComplete(async ({ metadata, file }) => {
      console.log('Upload complete:', file.url)
      // Save to database
      await db.insert(uploads).values({
        userId: metadata.userId,
        url: file.url,
        name: file.name,
      })
    }),
}

React Component

import { UploadButton } from '@uploadthing/react'

function ProfileForm() {
  return (
    <UploadButton
      endpoint="imageUploader"
      onClientUploadComplete={(res) => {
        console.log('Files:', res)
        setAvatarUrl(res[0].url)
      }}
      onUploadError={(error) => {
        alert(`Error: ${error.message}`)
      }}
    />
  )
}

5 minutes to working file uploads. Pre-built components, CDN delivery, and type-safe API.

Option 2: Presigned URLs + R2/S3 (Production)

For more control, generate presigned URLs and upload directly to storage:

Generate Presigned URL (Server)

// app/api/upload/route.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

const s3 = new S3Client({
  region: 'auto',
  endpoint: process.env.R2_ENDPOINT,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY!,
    secretAccessKey: process.env.R2_SECRET_KEY!,
  },
})

export async function POST(req: Request) {
  const { filename, contentType } = await req.json()
  const key = `uploads/${crypto.randomUUID()}-${filename}`

  const command = new PutObjectCommand({
    Bucket: process.env.R2_BUCKET,
    Key: key,
    ContentType: contentType,
  })

  const presignedUrl = await getSignedUrl(s3, command, { expiresIn: 600 })

  return Response.json({ presignedUrl, key })
}

Upload from Client

async function uploadFile(file: File) {
  // Step 1: Get presigned URL
  const { presignedUrl, key } = await fetch('/api/upload', {
    method: 'POST',
    body: JSON.stringify({ filename: file.name, contentType: file.type }),
  }).then(r => r.json())

  // Step 2: Upload directly to R2/S3 (bypasses your server)
  await fetch(presignedUrl, {
    method: 'PUT',
    body: file,
    headers: { 'Content-Type': file.type },
  })

  // Step 3: Save reference in database
  return `${process.env.NEXT_PUBLIC_CDN_URL}/${key}`
}

Pros: Files go directly to storage (not through your server). Scalable. Cheap.

Option 3: Vercel Blob

import { put } from '@vercel/blob'

export async function POST(req: Request) {
  const form = await req.formData()
  const file = form.get('file') as File

  const blob = await put(file.name, file, { access: 'public' })

  return Response.json({ url: blob.url })
}

Simplest for Vercel users. Automatic CDN. But more expensive per GB than R2.

Image Optimization

With Next.js Image Component

import Image from 'next/image'

<Image
  src={uploadedImageUrl}
  alt="User avatar"
  width={200}
  height={200}
  quality={80}
/>

Next.js automatically optimizes, resizes, and converts to WebP.

With Cloudflare Image Resizing

https://yourdomain.com/cdn-cgi/image/width=200,height=200,quality=80/uploads/avatar.jpg

File Validation

Server-Side (Critical)

const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf']
const MAX_SIZE = 10 * 1024 * 1024 // 10MB

function validateFile(file: File) {
  if (!ALLOWED_TYPES.includes(file.type)) {
    throw new Error('Invalid file type')
  }
  if (file.size > MAX_SIZE) {
    throw new Error('File too large')
  }
}

Client-Side (UX)

<input
  type="file"
  accept="image/jpeg,image/png,image/webp"
  onChange={(e) => {
    const file = e.target.files?.[0]
    if (file && file.size > 10 * 1024 * 1024) {
      alert('File must be under 10MB')
      return
    }
    handleUpload(file)
  }}
/>

Always validate on the server. Client-side validation is for UX only.

Drag and Drop Upload

function DropZone({ onUpload }) {
  const [isDragging, setIsDragging] = useState(false)

  return (
    <div
      onDragOver={(e) => { e.preventDefault(); setIsDragging(true) }}
      onDragLeave={() => setIsDragging(false)}
      onDrop={(e) => {
        e.preventDefault()
        setIsDragging(false)
        const files = Array.from(e.dataTransfer.files)
        files.forEach(onUpload)
      }}
      className={`border-2 border-dashed rounded-lg p-8 text-center
        ${isDragging ? 'border-blue-500 bg-blue-50' : 'border-gray-300'}`}
    >
      Drag files here or <button>browse</button>
    </div>
  )
}

FAQ

Should files go through my server?

No. Use presigned URLs to upload directly to storage. Your server generates the URL, the client uploads directly. Saves bandwidth and avoids request size limits.

How do I handle large files?

Use multipart uploads. The S3 SDK handles this automatically for files >5MB. Set appropriate timeout and chunk size.

Which storage should I use?

UploadThing for speed. R2 for cost. Vercel Blob for simplicity. S3 for enterprise.

Bottom Line

Use UploadThing for fast setup (5 minutes). Use presigned URLs + Cloudflare R2 for production scale (cheapest, most control). Always validate files server-side. Use Next.js Image component for automatic optimization.

Get AI tool guides in your inbox

Weekly deep-dives on the best AI coding tools, automation platforms, and productivity software.