Next.js cookbook · Intermediate

Upload files with a Server Action

Let users upload an avatar through a form that works even before JavaScript loads.

Approach

Read the File from FormData in a Server Action, validate size and type (and sniff the magic bytes for images), then stream it to object storage under a generated key. Server Actions have a default body size limit (1 MB) — raise it deliberately in next.config or use presigned direct uploads for larger files.

Solution

// app/settings/actions.ts
"use server";
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import { randomUUID } from "node:crypto";
import { revalidatePath } from "next/cache";

const s3 = new S3Client({});
const TYPES: Record<string, string> = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp" };

export async function uploadAvatar(_prev: { error?: string }, formData: FormData) {
  const session = await verifySession();
  const file = formData.get("avatar");
  if (!(file instanceof File) || file.size === 0) return { error: "Choose an image" };
  if (file.size > 2 * 1024 * 1024) return { error: "Max 2 MB" };
  const ext = TYPES[file.type];
  if (!ext) return { error: "PNG, JPEG or WebP only" };

  const bytes = Buffer.from(await file.arrayBuffer());
  const key = `avatars/${session.userId}/${randomUUID()}.${ext}`;
  await s3.send(new PutObjectCommand({ Bucket: process.env.BUCKET!, Key: key, Body: bytes, ContentType: file.type }));
  await db.user.update({ where: { id: session.userId }, data: { avatarKey: key } });
  revalidatePath("/settings");
  return {};
}
// next.config.ts
const config = { experimental: { serverActions: { bodySizeLimit: "3mb" } } };

Pitfalls

  • Trusting file.type alone for security-sensitive processing (re-encode images with sharp).
  • Large uploads through Server Actions (memory, timeouts).
  • Public buckets exposing every user's files.

Variations

  • Presigned direct-to-S3 uploads for large files.
  • UploadThing or similar services.

Further reading: Next.js: serverActions config

Reference: https://nextjs.org/docs/app/api-reference/config/next-config-js/serverActions

More Next.js recipes