DEV Community

Ahad Ali
Ahad Ali

Posted on

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

Example Link
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": []
  }
]
Enter fullscreen mode Exit fullscreen mode

🔑 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

Enter fullscreen mode Exit fullscreen mode

🧠 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" },
  });
}
Enter fullscreen mode Exit fullscreen mode

👉 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>
  );
}

Enter fullscreen mode Exit fullscreen mode

⚠️ 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
Enter fullscreen mode Exit fullscreen mode

✅ 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!

Top comments (1)

Collapse
 
lotfijb profile image
Lotfi Jebali

good writing, insightful