Upload any documents to amazon s3 hassle free and for free in Next JS (App router) with drag and drop feature.

Upload any documents to amazon s3 hassle free and for free in Next JS (App router) with drag and drop feature.

The Guide on How to upload PDF, Image or any document in Amazon s3 for free - Next JS (App router).

In this guide, we'll tackle the common headache of setting up file uploads in your Next.js app, especially when it comes to integrating with Amazon S3. Follow along as I show you how to effortlessly upload documents to Amazon S3 from your Next.js app, completely free of charge.

I will doing this with the help of the upload thing.

Create your Next JS app

npx create-next-app@latest

refer the next docs for more : Next JS

  • I will be using typescript throughout the guide.

  • For styling, i will be using tailwind CSS

  • You can customize as you wish

Set up Shadcn for wiring up the UI (minimal setup)

I am using shadcn, to wire up the UI. Shadcn is a Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.

pnpm dlx shadcn-ui@latest init
  • This will initialize shadcn. Soon after initializing you will see a folder called components
    See more on : shadcn

The UploadButton

This is a reusable component i will creating, which you can use to handles uploads

'use client'
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import React, { useState } from "react";

const UploadButton = () => {
  const [isOpen, setIsOpen] = useState<boolean>(false);

  return (
    <Dialog
      open={isOpen}
      onOpenChange={(v) => {
        if (!v) {
          setIsOpen(v);
        }
      }}
    >
      <DialogTrigger onClick={() => setIsOpen(true)} asChild>
        <Button>Upload Your Report to chat with it</Button>
      </DialogTrigger>

      <DialogContent>
        <UploadDropZone />
      </DialogContent>
    </Dialog>
  );
};


export default UploadButton;

Note :

  • If you have been developing on Next JS, then the above code is just self explanatory.

  • The things to note is that the Button , Dialog ,component. That is from shadcn

      pnpm dlx shadcn-ui@latest add button
      pnpm dlx shadcn-ui@latest add dialog
    
  • Also you might have noticed the UploadDropZone component.

UploadDropZone component

First we need a place or a component for the user to select and upload files. For that i will be using the react-dropzone

pnpm i react-dropzone

After installing we can give life to UploadDropZone component. It is a normal regular component. I am creating it just above the UploadButton '

There are two ways of using the Dropzone :

  • Either use the hook :

      import {useDropzone} from 'react-dropzone'
    
  • or the wrapper component for the hook, which I have used

import Dropzone from "react-dropzone";

const UploadDropZone = () => {
  return (
    <Dropzone multiple={false}>
      {({ getRootProps, getInputProps, acceptedFiles }) => (
        <div
          {...getRootProps()}
          className="border h-64 m-4 borrder-dashed border-gray-300 rounded-lg"
        >
          <div className="flex items-center justify-center h-full w-full">
            <label
              htmlFor="dropzone-file"
              className="flex flex-col items-center justify-center w-full h-full rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100"
            >
              <div className="flex flex-col items-center justify-center pt-5 pb-6">
                <p className="mb-2 text-sm text-zinc-700">
                  <span className="font-semibold">Click to upload</span> or drag
                  and drop
                </p>
                <p className="text-xs text-zinc-500">
                  Your medical record (PDF)
                </p>
              </div>

              {/* Upload button, and tracking */}

              {acceptedFiles && acceptedFiles[0] ? (
                <div className="max-w-xs bg-white flex items-center rounded-md overflow-hidden outline outline-[1px] outline-zinc-200 divide-zinc divide-zinc-200">
                  <div className="px-3 py-2 h-full grid place-items-center">
                    <div className="px-3 py-2 h-full text-sm truncate">
                      {acceptedFiles[0].name}
                    </div>
                  </div>
                </div>
              ) : null}

              <input
                {...getInputProps()}
                type="file"
                id="dropzone-file"
                className="hidden"
              />
            </label>
          </div>
        </div>
      )}
    </Dropzone>
  );
};

Note :

  • getRootProps: Function — Returns the props you should apply to the root drop container rendered

  • getInputProps: Function — Returns the props you should apply to hidden file input rendered

  • acceptedFiles : — Return the accepted file details

Setting up our storage

For storing the uploaded documents we will be using uploadthing. It is like a wrapper around the amazon s3, and its not complicated and its beginner friendly to use.

What to do :

  • Go to Upload thing website : UploadThing

  • Create your free account

  • Create a new app by clicking on the button

  • Enter the details and ...done !

  • You have successfully managed to create your cloud storage

  • NB : For large scale storages, use the Amazon s3 directly as it is more scalable and cheaper.

Setting up UploadThing onto our code:

Do the installation :

pnpm add uploadthing @uploadthing/react

Now set up the API keys

Create a file .env and add the information that's provided by the uploadthing under API keys.

Create files as below

core.ts at app/api/uploadthing/core.ts

//core.ts

import { createUploadthing, type FileRouter } from "uploadthing/next";
import { UploadThingError } from "uploadthing/server";

const f = createUploadthing();

export const ourFileRouter = {

  pdfUploader: f({ pdf: { maxFileSize: "4MB" } })

    .onUploadComplete(async ({ metadata, file }) => {
      // This code RUNS ON YOUR SERVER after upload
      // Perform the operations you want to do with the file here

      console.log("Upload complete, File :: ", metadata);
      console.log("file url", file.url);

      // Whatever is returned here is sent to the clientside `onClientUploadComplete` callback
      return { uploadedBy: file.url };
    }),
} satisfies FileRouter;

export type OurFileRouter = typeof ourFileRouter;

Note :

  • As of now i am just adding the functionality to upload pdf only

  • You can configure for any types of files that you want to upload by changing :

  •   //Change the pdf to image or whatever type you want.
      // Also change the variable name accordingly
    
      //eg : for pdf
      pdfUploader: f({ pdf: { maxFileSize: "4MB" } })
    
      //eg : for image
      imageUploader: f({ image: { maxFileSize: "4MB" } })
    
  • route.ts at app/api/uploadthing/route.ts
//route.ts

import { createRouteHandler } from "uploadthing/next"; 
import { ourFileRouter } from "./core";

// Export routes for Next App Router
export const { GET, POST } = createRouteHandler({
  router: ourFileRouter,
  config: {},
});
  • uploadthing.ts at src/lib/uploadthing.ts
import { generateReactHelpers } from "@uploadthing/react";
import type { OurFileRouter } from "@/app/api/uploadthing/core";

export const { useUploadThing } =
  generateReactHelpers<OurFileRouter>();

Go back to UploadButton.tsx

We will be adding

  • the useUploadThing

  • a toaster to show the the user, if it successful or not, for that we will utilizing shadcn again

    •   pnpm dlx shadcn-ui@latest add sonner
      
    • Also for toaster to work, we need to add the toast component in layout.ts

    •     return (
              <html lang="en">
                <body className={inter.className}>{children}</body>
                <Toaster richColors/>
              </html>
            );
      
  • And with that the toast is successfully added

  • Installed 'lucide-react' for the loader

      pnpm install lucide-react
    
  • This is the updated UploadButton component

//UploadButton.tsx

"use client";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { useUploadThing } from "@/lib/uploadthing";
import React, { useState } from "react";
import { toast } from "sonner";

import Dropzone from "react-dropzone";
import { useRouter } from "next/navigation";
import { Divide, Loader2 } from "lucide-react";

const UploadDropZone = () => {
  const { startUpload } = useUploadThing("pdfUploader");
  const [isUploading, setIsUploading] = useState<boolean>(false);
  const router = useRouter();

  return (
    <Dropzone
      multiple={false}
      onDrop={async (acceptedFile) => {
        setIsUploading(true);

        const res = await startUpload(acceptedFile);

        if (res) {
          toast.success("File uploaded successfully");
        } else {
          toast.error("File upload Error");
        }

        const [fileResponse] = res;
        const key = fileResponse?.key;
        console.log("Key", key);

        // This is how the url looks like :
        const urlLink = `https://utfs.io/f/${key}`;
        router.push(fileResponse.url);

      }}
    >
      {({ getRootProps, getInputProps, acceptedFiles }) => (
        <div
          {...getRootProps()}
          className="border h-64 m-4 borrder-dashed border-gray-300 rounded-lg"
        >
          <div className="flex items-center justify-center h-full w-full">
            <label
              htmlFor="dropzone-file"
              className="flex flex-col items-center justify-center w-full h-full rounded-lg cursor-pointer bg-gray-50 hover:bg-gray-100"
            >
              <div className="flex flex-col items-center justify-center pt-5 pb-6">
                <p className="mb-2 text-sm text-zinc-700">
                  <span className="font-semibold">Click to upload</span> or drag
                  and drop
                </p>
                <p className="text-xs text-zinc-500">Upload any files.</p>
              </div>

              {/* Upload button, and tracking */}
              {isUploading ? (
                <div className="flex gap-1 items-center justify-center text-sm text-zinc-800 text-center pt-2">
                  <Loader2 className="h-3 w-3 animate-spin" />
                  Loading !
                </div>
              ) : null}

              {acceptedFiles && acceptedFiles[0] ? (
                <div className="max-w-xs bg-white flex items-center rounded-md overflow-hidden outline outline-[1px] outline-zinc-200 divide-zinc divide-zinc-200">
                  <div className="px-3 py-2 h-full grid place-items-center">
                    <div className="px-3 py-2 h-full text-sm truncate">
                      {acceptedFiles[0].name}
                    </div>
                  </div>
                </div>
              ) : null}

              <input
                {...getInputProps()}
                type="file"
                id="dropzone-file"
                className="hidden"
              />
            </label>
          </div>
        </div>
      )}
    </Dropzone>
  );
};

const UploadButton = () => {
  const [isOpen, setIsOpen] = useState<boolean>(false);

  return (
    <Dialog
      open={isOpen}
      onOpenChange={(v) => {
        if (!v) {
          setIsOpen(v);
        }
      }}
    >
      <DialogTrigger onClick={() => setIsOpen(true)} asChild>
        <Button>Upload Your Report to chat with it</Button>
      </DialogTrigger>

      <DialogContent>
        <UploadDropZone />
      </DialogContent>
    </Dialog>
  );
};

export default UploadButton;
  • With this I hope you successfully managed to upload the files. To view the files you can always check the dashboard in uploadthing website

Conclusion

  • The code for the project : Github.

  • If you find any mistakes or if you have any better suggestions, feel free to mention them . Always a learner and always ready to adapt

  • Follow me for more blogs and to know more on development stuffs.

Aniz B N

#