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": []
}
]
🔑 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!
Top comments (1)
good writing, insightful