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.

· 7 min read
Cornelius Emase

Cornelius Emase

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.

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.

Image

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.

Image

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 / YesWould you like to use Tailwind CSS?No / YesWould you like to use `src/` directory?No / YesWould you like to use App Router? (recommended)No / YesWould 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.

Image

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.

Image

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!

share

Cornelius Emase

Software Engineer | Product Manager | Technical writer |