Next.js Server Action: A Guide to Uploading Files to AWS S3

Siam Ahnaf
5 min readFeb 18, 2024

In my previous tutorial I describe the details actually what is Server Actions, I build a contact form and send email using server actions. In that tutorial I use server action into a server component. But today I will do slightly different, and I will create server action for uploading file into AWS s3 from a client component.

In react if we want to upload file into AWS s3, we need to enable CORS from AWS, and that mostly that is a difficult for enabling CORS from AWS. That’s why in this tutorial, we learn how to upload file from direct NextJS application without enabling CORS into AWS and today we will use NextJS Server Action.

What is server action?

Next.js Server Actions are asynchronous functions that run on the server but can be invoked from both server-side and client-side components. They are useful for handling form submissions and data mutations in Next.js applications.

Prerequisites

Today I will use: -

  • Next.js 14
  • For selecting file, I will use react-image-uploading npm package.
  • For uploading file into AWS, I will use my own npm package nodejs-s3-typescript or react-s3-typescript

Setting up the project

First, create a new Next.js project using create-next-app:

npx create-next-app next-server-actions

Then, install react-image-uploading and nodejs-s3-typescript as a dependency:

npm install react-image-uploading nodejs-s3-typescript

Next, create a .env.local file in the root of your project and add your AWS API key & Secret Key as an environment variable:

AWS_BUCKET_NAME=bucket_name
AWS_REGION=bucket_region
AWS_ACCESS_ID=access_id
AWS_SECRET_ID=secret_id

So, you need to create bucket into AWS s3. Follow my following step for doing that-

Step 1: Set up an S3 Bucket

You must first create an S3 bucket in your AWS account in order to use Amazon S3. By entering into the AWS Management Console and going to the S3 service, you can accomplish this. To create a new bucket, click the “Create Bucket” button. Choose the region where you wish to keep your data and give it a unique name.

Step 2: Make bucket public

Now we need to edit the bucket policy to make our file publicly accessible. Open your bucket and go to the permission tab and click on the edit button of the Bucket policy and paste the below JSON into it.

Note: Replace bucket-name with the name of your bucket.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicListGet",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::bucket-name",
"arn:aws:s3:::bucket-name/*"
]
}
]
}

That’s it, here we don’t enable the CORS policy as we will use the NextJS Server Action.

Now we need to create IAM user for getting the AWS secret id and access ID. Please fill out the environment variable with all the data.

Now Create a action file fileUploader.ts and add following code-

"use server"
import { s3Client } from "nodejs-s3-typescript";

//S3 Config
const s3Config = {
bucketName: process.env.AWS_BUCKET_NAME as string,
region: process.env.AWS_REGION as string,
accessKeyId: process.env.AWS_ACCESS_ID as string,
secretAccessKey: process.env.AWS_SECRET_ID as string
}

export const UploadImage = async (formData: FormData) => {
try {
const file = formData.get("file") as File;
const folderName = formData.get("folderName") as string;
const s3 = new s3Client({
...s3Config,
dirName: folderName
});
const res = await s3.uploadFile(Buffer.from(await file.arrayBuffer()));
return res;
} catch (e) {
return "Image Upload failed"
}
}

So here we are using AWS latest s3 client SDK version 3. It is updated one. In this action file I declare "user server" decorative keyword for running the function on server.

Now cretae a client component as I said before today we are going to use client component for using the server action. I am creating a file called ImageUploader.tsx -

"use client"
import { useState } from "react";
import ImageUploading, { ImageListType } from "react-images-uploading";
import Image from "next/image";

//Actions
import { UploadImage } from "./fileUploader";


const ImageUploader = () => {
//State
const [image, setImage] = useState<ImageListType>([]);

//OnImageChange
const onImageChange = async (imageList: ImageListType) => {
await setImage(imageList);
if (imageList.length > 0 && imageList[0].file as File) {
const formData = new FormData();
formData.append("file", imageList[0].file as File);
formData.append("folderName", "nextjs-server-action");
//Here I am calling the server action function
const data = await UploadImage(formData);
console.log(data)
}
}
return (
<div>
<ImageUploading
value={image}
onChange={onImageChange}
multiple={false}
maxFileSize={5000000}
>
{({
imageList,
onImageUpload,
isDragging,
dragProps
}) => (
<div>
{imageList.length === 0 &&
<button
onClick={onImageUpload}
{...dragProps}
className={`border-2 border-dashed w-full rounded-md text-center py-12 hover:border-main ${isDragging ? "border-main" : "border-gray-300"}`}
type="button"
>
<div className={`${isDragging ? "pointer-events-none" : ""}`}>
<Image src="/upload.png" width={90} height={90} alt="Upload" className="w-[70px] mx-auto" />
<h6 className="text-base font-medium text-gray-600">Drop your image here, or <span className="text-main">browse</span></h6>
</div>
</button>
}
{imageList.length > 0 &&
imageList.map((image, i) => (
<div key={i} className={`relative cursor-pointer group rounded-md overflow-hidden`}>
<Image src={image["fvr-url"]} alt="Image" width={400} height={400} className="w-full object-cover object-top" />
</div>
))
}
</div>
)}
</ImageUploading>
</div>
);
};

export default ImageUploader;

Here in this component, I add "use client" keyword that’s means it is now a client components. I import the server action function and I call that function into onImageChange event handler-

const onImageChange = async (imageList: ImageListType) => {
await setImage(imageList);
if (imageList.length > 0 && imageList[0].file as File) {
const formData = new FormData();
formData.append("file", imageList[0].file as File);
formData.append("folderName", "nextjs-server-action");
const data = await UploadImage(formData);
console.log(data)
}
}

Here I am sending file as form data into server action. on the other hand, from the server function receiving that form data and trying to upload file from server.

Conclusion

In this post, we learned how to use Next.js Server Actions to upload file into AWS s3. We created a simple image uploader area for selecting image. And lastly, we use server action for uploading file into AWS s3.

Next.js Server Actions are a powerful feature that simplifies the development of Next.js applications by eliminating the need to create API endpoints. They also provide type safety, code reuse, and performance benefits.

If you want to learn more about Next.js Server Actions, you can check out the official documentation or some of the tutorials and articles on the topic.

--

--

Siam Ahnaf

I'm Siam Ahnaf, a passionate developer who loves to learn new things and create awesome projects. I enjoy working on both front-end and back-end development.