Upload Files To AWS S3 From Next.js 14 (App Router)

June 2, 2024

AWS S3 is a popular choice for storing files in the cloud. Next.js is one of the popular choices for building frontend apps.

In this article, we will learn how to combine these two popular choices to build a file upload app.

In order to upload files, we need to do following three things.

  1. Create an S3 Bucket

  2. Create an IAM role to access the S3 Bucket

  3. Build an Next.js app to upload files

Let's get started

1. Create a S3 Bucket

  1. To go your AWS console and go to the S3 dashboard.

  2. Click on the Create bucket button.

  3. On the next screen, keep the default settings i.e. ACLs disabled and Block all public access` and click the Create bucket button.

  4. Our bucket is ready.

2. Create an IAM Role

  1. Go to the IAM dashboard.

  2. Click on Create user.

  3. In the User details screen, add a name to your user and click Next.

  4. On the Set permissions screen, select Attach policies directly.

  5. Click on Create policy (located right below the Attach policies directly option).

  6. On the Specify permissions screen, select the following permissions.

    s3:DeleteObject, s3:GetObject, s3:ListBucket, s3:PutObject, s3:PutObjectAcl

  7. Under the Resources section, we need to add the ARN to our bucket.

  8. Click on Add ARNs and fill in the relevant details and click Add ARNs. You need to do it for both Bucket and Object.

    1. Bucket

    2. Object

  9. Come back to the Create user screen in IAM, attach the freshly created policy to this user and click `Create user`.

  10. Now select the new user, go to the Security credentials tab, scroll down to the Access keys section and click on a Create access key.

  11. Select any appropriate option on the next screen called Access key best practices & alternatives and click Next.

  12. In the Set description tag screen, click on Create access key.

  13. You will get your access keys on the next screen

  14. We will use this key to access the AWS S3 bucket from our app.

3. Build a Next.js app

Create a new app by running the following command. You can name the app whatever, I have named it next-s3-demo.

npx create-next-app next-s3-demo

Select the following configuration while creating the app.

Once done, start the development server by running

npm run dev

Removing the boilerplate code.

From app/globals.css, remove everything except the following.

@tailwind base;
@tailwind components;
@tailwind utilities;

From app/page.tsx, remove everything inside the main tag. It should look like the following.

<main className="">

</main>

Add a form to upload file

In app/page.tsx, copy paste the following code, inside the main tag.

<form action={onSubmit}>
    <input type="file" name="file" />
    <input type="submit" value="Upload" />
</form>

Create a server action to upload a file

Create a new file called action.ts inside the app/ folder. Add the following code to the file.

'use server'

import { S3Client } from "@aws-sdk/client-s3";
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
import { nanoid } from "nanoid";

export async function onSubmit(formData: FormData) {
    try {
        const client = new S3Client({
            region: process.env.AWS_REGION,
        })

        const { url, fields } = await createPresignedPost(client, {
            Bucket: process.env.AWS_BUCKET_NAME || '',
            Key: nanoid(),
        })

        const formDataS3 = new FormData();
        Object.entries(fields).forEach(([key, value]) => {
            formDataS3.append(key, value);
        })
        formDataS3.append('file', formData.get('file') as string);

        const uploadResponse = await fetch(url, {
            method: 'POST',
            body: formDataS3,
        })

        const response = await uploadResponse.text();
        console.log(response)

        if (uploadResponse.ok) {
            console.log('File uploaded successfully');
        } else {
            console.error('Failed to upload file');
        }
    } catch (err) {
        console.error(err);
    }
}

Here is what is happening in the above code:

  1. An AWS S3 client is created

  2. A one-time url is created so that you can upload the file without sharing credentials. This comes handy while letting users upload files from frontend.

  3. A new form data object is created and all the fields obtained from Step #2 are added to it.

  4. Add the file data to the form data created in Step #3.

  5. Issue a fetch request to upload the file.

  6. Parse the response.

Hook the server action to the form.

In the app/page.tsx file, use the newly created onSubmit server action to the in the form as shown below.

import { onSubmit } from "./action";

export default function Home() {
    return (
        <main className="">
            <form action={onSubmit}>
                <input type="file" name="file" />
                <input type="submit" value="Upload" />
            </form>
        </main>
    );
}

That's it! Now you can upload files via your app. The uploaded files will be visible inside the AWS S3 dashboard.

The entire codebase for this project is available on Github.

Introducing MediaLit

Using MediaLit, you can rapidly build a file upload facility in your serverless apps in record time. You don't even have to have an AWS account to upload files.

You can get started for free and upgrade later on. See our 5 minutes quick start.