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 shadcnpnpm 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 renderedgetInputProps
: Function — Returns the props you should apply to hidden file input renderedacceptedFiles
: — 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.
#