File Uploads & Storage
Handle file uploads and object storage.
The template includes one authenticated image upload route. It writes files to the local filesystem and returns public URLs — a working starting point, but not durable storage. Treat it as the integration point for S3, R2, or Vercel Blob.
What it provides
- An authenticated
POST /api/storage/upload-imageroute. - Image-only validation and collision-free filenames.
- Returned
/uploads/...URLs for the saved files.
Important files
| File | Role |
|---|---|
src/app/api/storage/upload-image/route.ts | Upload handler |
public/uploads/ | Where files are written (created at runtime) |
How the route works
export async function POST(request: Request) {
const session = await auth.api.getSession({ headers: await headers() });
if (!session?.user) return /* 401 Unauthorized */;
const formData = await request.formData();
const files = formData.getAll("files");
// …writes each file, returns its URL
}Step by step:
- Auth required — the route reads the Better Auth session and returns
401if there's no user. - Multipart form — it reads the
filesfield (getAll, so multiple files are supported) and rejects an empty upload with400. - Image-only — each entry must be a
Filewhose type starts withimage/; anything else is rejected. - Safe filenames — the extension is mapped from the MIME type
(jpg/png/webp/gif), and the name is a
crypto.randomUUID()so uploads never collide or expose the original name. - Write + respond — files are written to
public/uploads/, and the route returns{ success: true, data: { urls: ["/uploads/<uuid>.<ext>"] } }.
The durability problem
Local disk is not durable in production. Files are written to
public/uploads, which works in local dev but does not persist on
Vercel, serverless, or containerized deploys — the filesystem is ephemeral
and per-instance. Uploads will vanish on redeploy/scale and won't be shared
across instances. Do not rely on this route in production as-is.
Integrating object storage
Swap the filesystem write for an object-storage SDK (AWS S3, Cloudflare R2, Vercel Blob, …). Keep the route's auth check and validation; replace only the "write file + build URL" part:
Add the provider SDK + credentials
Install the SDK and add its credentials as server-only env vars (see Environment Variables).
Replace the write
Instead of writeFile(...) to public/uploads, upload the buffer to your
bucket and use the returned/object URL.
Return the real URL
Push the bucket/CDN URL into the urls array the route already returns, so
callers need no changes.
Production requirements
Before exposing uploads to real users, add:
- Size limits — reject oversized files (the route does not cap size).
- Stronger validation — verify real content type, not just the declared MIME, and consider re-encoding images.
- Access policy — decide public vs. signed/private URLs per object.
- Cleanup — delete orphaned files when records are removed.
- CDN — serve through a CDN for performance.
- Abuse protection — rate-limit the route and scope it to the right users.
