How to add file uploads to shadcn/ui in Next.js (direct to S3 or R2)
File uploads look simple until you ship them: progress bars, retries when the network drops, files too big for one request, keeping bytes off your server, and a UI that matches the rest of your shadcn app. This guide us
File uploads look simple until you ship them: progress bars, retries when the network drops, files too big for one request, keeping bytes off your server, and a UI that matches the rest of your shadcn app.
This guide uses UploadCN, an open-source upload kit for shadcn/ui, to add production-ready uploads to a Next.js App Router project in a few minutes.
1. Add the upload component
In a project set up with the shadcn CLI:
npx shadcn@latest add @uploadcn/file-upload @uploadcn/upload-config
The CLI copies the components into components/, in your project's shadcn style and icon library, and installs the small @uploadcn/core and @uploadcn/react packages that handle the upload logic.
2. Add the upload route
npx shadcn@latest add @uploadcn/upload-route
This creates app/api/upload/route.ts. It doesn't receive files: it signs each upload, so the browser sends bytes straight to your bucket. Add your credentials:
S3_BUCKET=my-uploads
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
For Cloudflare R2, use the R2 variables instead (see the docs). Allow PUT from your origin in the bucket's CORS settings and expose the ETag header.
3. Configure it once
Wrap your app so every upload component knows where files go:
// app/layout.tsx
import { UploadConfig } from "@/components/upload-config"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<UploadConfig>{children}</UploadConfig>
</body>
</html>
)
}
4. Use it
import { FileUpload } from "@/components/file-upload"
export default function Page() {
return <FileUpload accept="image/*" maxSize={10_000_000} maxFiles={5} />
}
You get drag and drop, a file list with previews and progress, retry and remove, validation, and uploads that resume when the connection comes back. Big files upload in parallel chunks.
What else it does
- Other storage: Cloudinary, local disk, or any API through
createAdapter - Virus scanning in the route (ClamAV, VirusTotal) and OCR
- 58 blocks: avatar crop, gallery, document checklist, chat attachments, CSV import and 30 animated uploaders
Docs and live demos: https://uploadcn.dev
Repo (MIT): https://github.com/uploadcn/uploadcn
If you try it, I'd love to hear what you'd improve.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.