Next.js file uploads made easy with uploadthing - An alternate to s3 bucket
This guide will show you how to simplify file uploads in your Next.js application using UploadThing, configured to use an alternate S3 bucket for storage.
Introduction
A much simpler way you could do uploads using uploadthing, be on the bleeding edge. Uploadthing has become more popular for its simplicity and benefits, and so in this guide we will do a demo showcasing how we can upload and serve the images.
Prerequisites
Before we begin, you should make sure you have the following:
- Node.js installed
- Have github account. If you don't have one, visit github.
- A basic understanding of Next.js (app router)
- Uploadthing account. You can create one if you don’t have one.
We’re then ready to go ahead.
Highlights
Here are some of the key points you will come up with:
- What uploadthing is, and the benefits of using it.
- Usage of uploadthing storage with NextJs.
- Step-by-step guide to uploading and retrieving images.
Try Kodaschool for free
Click below to sign up and get access to free web, android and iOs challenges.
What is Uploadthing?
Uploadthing provides services for file uploads, and serves for full stack Typescript applications. This ensures type safety in your applications, which is one of the great benefits that comes together with uploadthing. It has features like server-side authorization.
Benefits of using uploadthing
- Simplified File Hosting: UploadThing manages file hosting, making it easier for you to handle files without the hassle.
- Server-side Authorization: You retain control by hosting the endpoint on your own servers, avoiding bandwidth costs while ensuring security.
- Great Client Experience: With an open-source React client library, Uploadthing provides convenient components and hooks for seamless frontend file uploads.
Creating a uploadthing project
Head over to the uploadthing and create a project, name it and press the create app button to complete it.
That would be it, the next thing is to head to the `API Keys` section on the left to grab your secret keys, this would enable us to have access to the files we have on this project. We will have access to read and write, also delete files whenever we need it.
Setting up NextJs project
The next thing we need to do is set up Next.js project, going with type safety, we will do Typescript. In your commandline, paste the following command and answer a few questions on it.
npx create-next-app@latest --ts --use-npm <project-name-here>
// go to your project folder
cd <project-name>
The questions would look this way:
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
Creating a new Next.js app in <project-directory>
We will go with yes in all the questions. This will scanfold a folder structure like this:
my-nextjs-app/
├── app/
│ ├── layout.js
│ ├── page.js
│ ├── api/
│ │ └── route.js
│ ├── styles/
│ │ └── globals.css
│ ├── components/
│ └── utils/
├── public/
│ ├── favicon.ico
│ └── vercel.svg
├── .gitignore
├── next.config.js
├── package.json
├── README.md
└── package-lock.json
Setting Up UploadThing in Next.js
Install the Packages
We then have to install the uploadthing and it's react client library. Make sure you’re in your project's directory, open your command line and run the following command.
npm install uploadthing @uploadthing/react
Add Environment Variables
Create a .env.local
file in the root directory of your project folder. Add your UploadThing secret key in here.
UPLOADTHING_SECRET=sk_l***********************************************************************************skdjsd
UPLOADTHING_APP_ID=akjs******
Setting up the file File router
This is like a blueprint for handling our file uploads in our application. It defines what types of files we can upload, the maximum file size, and any server-side logic that should run before or after the upload.
Create a new file app/api/uploadthing/core.ts
and paste this code,
import { createUploadthing, type FileRouter } from "uploadthing/next";
import { UploadThingError } from "uploadthing/server";
const f = createUploadthing();
const auth = (req: Request) => ({ id: "fakeId" });
export const ourFileRouter = {
imageUploader: f({ image: { maxFileSize: "4MB" } })
.middleware(async ({ req }) => {
const user = await auth(req);
if (!user) throw new UploadThingError("Unauthorized");
return { userId: user.id };
})
.onUploadComplete(async ({ metadata, file }) => {
console.log("Upload complete for userId:", metadata.userId);
console.log("file url", file.url);
return { uploadedBy: metadata.userId };
}),
} satisfies FileRouter;
export type OurFileRouter = typeof ourFileRouter;
In the above code, we’re importing the necessary functions, and defining the functions.
f({ image: { maxFileSize: "4MB" } }): This configures the uploader to accept images with a maximum size of 4MB.
.middleware(async ({ req }) => { ... }): This function runs on the server before the upload. It uses the fake auth function to check if the user is authorized. If not, it throws an error. You can get your authorization function work here.
.onUploadComplete(async ({ metadata, file }) => { ... }):
This function runs after the upload is complete. It logs the user ID
and file URL
to the console and returns some metadata.
Create a route file inside the app/api/uploadthing/route.ts
Paste the following code.
import { createRouteHandler } from "uploadthing/next";
import { ourFileRouter } from "./core";
// Export routes for Next App Router
export const { GET, POST } = createRouteHandler({
router: ourFileRouter,
// Apply an (optional) custom config:
// config: { ... },
});
And finally exporting the file, and we would be able to use it in our other pages.
This code sets up your API to handle file upload requests at /api/uploadthing. It uses the configurations defined in ourFileRouter.
Add UploadThing's Styles
Uploadthing has a tailwind configuration, all we need to do is to wrap our Tailwind configuration with withUt
.
Have it look this way:
import { withUt } from "uploadthing/tw";
export default withUt({
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
});
The withUt
adds UploadThing-specific styles to your Tailwind configuration, ensuring your file upload components are styled correctly.
Create a utility file
We then have to create a new file in the src/utils/uploadthing.ts
.
Paste the following code:
import {
generateUploadButton,
generateUploadDropzone,
} from "@uploadthing/react";
import type { OurFileRouter } from "~/app/api/uploadthing/core";
export const UploadButton = generateUploadButton<OurFileRouter>();
export const UploadDropzone = generateUploadDropzone<OurFileRouter>();
The generateUploadButton
and generateUploadDropzone
create ready-to-use upload components that we can add to our UI.
That’s it with the configuration, we can now use the upload thing button in other components to upload files and serve them.
Let’s add the upload button now, create a component, image-upload.tsx
and have the following code in it,
"use client";
import { UploadButton } from "@/utils/uploadthing";
import Image from "next/image";
import { useState } from "react";
const ImageUpload = () => {
const [imageUrl, setimageUrl] = useState<string>("");
return (
<div>
<UploadButton
className="text-white font-bold py-2 px-4 rounded"
endpoint="imageUploader"
onClientUploadComplete={(res) => {
console.log("Files", res);
alert("Files uploaded");
setimageUrl(res[0].url);
}}
onUploadError={(error: Error) => {
alert(`ERROR! ${error.message}`);
}}
/>
{imageUrl.length ? (
<div>
<h2>Image Preview</h2>
<Image src={imageUrl} alt="Uploaded Image" width={500} height={300} />
</div>
) : 'nothing here, no image uploaded yet'}
</div>
);
};
export default ImageUpload;
Explanation
The UploadButton component from UploadThing simplifies file uploads in our Next.js application. It connects to a specific endpoint (in this case, imageUploader
), and handles both successful uploads and errors with customizable callbacks.
When we upload a file, it runs client-side, providing immediate feedback through alerts and console logs for completion or errors if we need to.
When an image is successfully uploaded, the URL returned, we store it in the state imageUrl
, and this URL
is then used to display a preview of the uploaded image with the Image component.
Don't forget to add the "use client;"
directive at the top of your file, since the UploadButton component needs to run on the client-side.
We can the import it in our main file page.tsx
import ImageUpload from "./components/image-upload";
export default function Home() {
return (
<main className="text-center bg-gray-200 min-h-screen text-gray-800">
<h1 className="text-3xl text-blue-500 font-bold py-4">Upload thing Learning & Next.Js</h1>
<ImageUpload />
</main>
);
}
UploadThing SSR Plugin
Yeah plugins, you may not want to see the loading state of the button, but an instant button ready to take in file uploads faster. All we gotta do is import it in our layout
file, have it look this way.
import { NextSSRPlugin } from "@uploadthing/react/next-ssr-plugin";
import { extractRouterConfig } from "uploadthing/server";
import { ourFileRouter } from "~/app/api/uploadthing/core";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<NextSSRPlugin
routerConfig={extractRouterConfig(ourFileRouter)}
/>
{children}
</body>
</html>
);
}
The final Product
Our final page would look like this, upload and serve, you could also catch the url and store it in your database and fetch it later.
When you head up to the dashboard, you’ll find the uploaded image right there, and that’s it.
There are a variety of button uploads we could use, you can try uploadDropZone and explore the magic.
Summary
In this guide, we covered how to integrate UploadThing with a Next.js project to upload and serve images/files. We discussed the benefits of using UploadThing, such as simplified file hosting, server-side authorization, and a great client experience. We then walked through setting up a Next.js project, installing UploadThing, configuring environment variables, creating a FileRouter, and creating components to handle file uploads.
Conclusion
By following this guide, you should now have a Next.js application capable of uploading and serving images/files using UploadThing. Enjoy the simplicity and benefits of using UploadThing in your projects!