Back to Blog

Upload Files from Next.js to AWS S3 Using Presigned URLs

Ahad Ali
11/11/2025
4 min read
Upload Files from Next.js to AWS S3 Using Presigned URLs

In modern web apps, users often need to upload images, documents, or videos. Amazon S3 is one of the best cloud storage options — but directly uploading files from a frontend like Next.js can expose your AWS credentials.

In this tutorial, you’ll learn how to securely upload files from a Next.js app to AWS S3 using presigned URLs, step by step.

Table of Contents

Step 1 – Create and Configure an S3 Bucket Step 2 – Add Environment Variables Step 3 – Install AWS SDK v3 Step 4 – Create API Route for Presigned URL Step 5 – Upload from Frontend Step 6 – Common Errors & Fixes Step 7 – Final Directory Structure Step 8 – Conclusion

🧠 Why Presigned URLs?

When you generate a presigned URL on the server, it gives the client temporary permission to upload files to S3 — without exposing your AWS secret keys. ✅ Secure ✅ Fast (direct upload to AWS, not via backend) ✅ Scalable for production

⚙️ Prerequisites

Before you begin: AWS account (with S3 access) Node.js & Next.js project setup Basic understanding of environment variables

🔧 Step 1: Create and Configure an S3 Bucket

Go to your AWS Console → S3 → Create Bucket

Choose a unique bucket name

Enable required region (e.g., ap-south-1)

In Permissions, uncheck “Block all public access” if you want public access for uploaded files (optional).

Add this CORS configuration (important):

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT"],
    "AllowedOrigins": ["*"],
    "ExposeHeaders": []
  }
]

🔑 Step 2: Add Environment Variables

Create a .env.local file in your Next.js project and add: AWS_ACCESS_KEY_ID=your_access_key AWS_SECRET_ACCESS_KEY=your_secret_key AWS_REGION=ap-south-1 S3_BUCKET_NAME=your_bucket_name

Make sure not to commit these to GitHub (they’re secret).

🧩 Step 3: Install AWS SDK v3

Run this command:

npm install @aws-sdk/client-s3

🧠 Step 4: Create an API Route for Presigned URL

Inside your Next.js app, create the following file: /app/api/upload-url/route.js (for Next.js 13+ with App Router)

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({
  region: process.env.AWS_REGION,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
});

export async function GET(req) {
  const { searchParams } = new URL(req.url);
  const fileName = searchParams.get("file");
  const fileType = searchParams.get("type");

  const command = new PutObjectCommand({
    Bucket: process.env.S3_BUCKET_NAME,
    Key: fileName,
    ContentType: fileType,
  });

  const signedUrl = await getSignedUrl(s3, command, { expiresIn: 60 });

  return new Response(JSON.stringify({ url: signedUrl }), {
    headers: { "Content-Type": "application/json" },
  });
}

👉 This route: Generates a signed URL valid for 60 seconds

Returns it to the client so the file can be uploaded securely

🧠 Step 5: Upload from Frontend

Now, create a simple upload component: /app/upload/page.jsx

"use client";

import { useState } from "react";

export default function UploadPage() {
  const [file, setFile] = useState(null);
  const [uploading, setUploading] = useState(false);

  const handleUpload = async () => {
    if (!file) return;

    setUploading(true);

    // 1. Request presigned URL from API
    const res = await fetch(
      `/api/upload-url?file=${file.name}&type=${file.type}`
    );
    const { url } = await res.json();

    // 2. Upload file directly to S3
    const upload = await fetch(url, {
      method: "PUT",
      body: file,
      headers: { "Content-Type": file.type },
    });

    if (upload.ok) {
      alert("✅ File uploaded successfully!");
    } else {
      alert("❌ Upload failed.");
    }

    setUploading(false);
  };

  return (
    <div className="flex flex-col items-center mt-20">
      <input
        type="file"
        onChange={(e) => setFile(e.target.files[0])}
        className="mb-4"
      />
      <button
        onClick={handleUpload}
        disabled={uploading}
        className="bg-blue-600 text-white px-4 py-2 rounded"
      >
        {uploading ? "Uploading..." : "Upload"}
      </button>
    </div>
  );
}

⚠️ Step 6: Common Errors & Fixes

Error Cause Fix 403 Forbidden Wrong IAM permissions Ensure bucket allows PutObject for your AWS user CORS error CORS policy missing in bucket Add correct CORS config (see Step 1) AccessDenied Wrong region or key Check .env.local values

🧾 Step 7: Final Directory Structure

app/
 ├─ api/
 │   └─ upload-url/
 │       └─ route.js
 ├─ upload/
 │   └─ page.jsx
.env.local

✅ Conclusion

You’ve successfully learned how to upload files from Next.js to AWS S3 using presigned URLs. This approach is secure, efficient, and scalable for production use. If you found this helpful: ⭐ Check out the full source code on GitHub: github.com/ahadali0500/nextjs-s3-upload

🧠 Follow me for more DevOps + Next.js guides!

Share this post: