Next.js and Supabase: How to Store and Serve Images
This guide will give you the overview of how to upload and retrieve images using Supabase Storage with Next.js. We will cover a step-by-step process, getting your hands-on.
Introduction
To build powerful applications, at some point you may need to store your data on cloud storage providers, including Microsoft Azure, AWS buckets and so many more. In this guide, we will look into one of the popular used storages, Supabase storage. We will build a simple profile form, to upload our profile picture. Every step you need to take to get started with Supabase storage.
Prerequisites
Before we begin, you should make sure you have the following:
- Node.js involved
- A basic understanding of Next.js
- A Supabase account. You can create it here if you don’t have one.
With all these set, we are ready to go.
Highlights
Here are some of the key points you will come up with:
- What Supabase storage is, and the benefits of using it.
- Usage of Supabase storage with NextJs.
- Step-by-step guide to uploading and retrieving images
What is Supabase storage
Supabase is an open-source Firebase alternative that provides a variety of backend services, including a powerful storage solution. It is a part of the Supabase suite, which will allow you to store and serve any type of files, including images, videos, and documents.
It is a simple and scalable solution for handling file storage in web applications.
Try Kodaschool for free
Click below to sign up and get access to free web, android and iOs challenges.
Benefits of Supabase Storage
- Easy to Use: Supabase has a simple API for file uploads and downloads.
- Secure: It also has access control and permission settings to keep your data safe.
- Scalable: They are built with scalability in mind, to handle growing amounts of data efficiently.
Usage with Next.js
We’ll walk through the process of integrating Supabase Storage with a Next.js application together with you. This will include setting up the project, connecting to Supabase, and implementing file upload and retrieval functionality. We will do a simple image upload and retrieval, but you’ll learn a lot along the way.
Bootstrapping Next.js
You will have to create a Supabase project on the dashboard and fill in the required details and wait until it launches.
The next thing is to get the project URL and anon key from the API settings.
- Head to the API Settings page in the Dashboard.
- Find your project URL, anon, and service_role keys on this page.
Initializing our NextJs project
First, we need to set up a new Next.js project, we will do a TypeScript project. Open the command line and runt the following code:
npx create-next-app@latest --ts --use-npm <project-name-here>
// go to your project folder
cd <project-name>
We will scaffold the project structure this way, using tailwind for styling, app directory etc. …
✔ 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>
The next thing we have to do is We then install the Supabase client library.
npm install @supabase/supabase-js
Environment variables
We have to create a .env.local
file where we can store our keys we saved earlier, including API URL, and the anon key. Paste the following replacing with your actual keys.
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
Supabase utilities: Connecting with Supabase client
Supabase has two different types of clients to access its services, including:
- Client component client. This is used to access Supabase from Client Components, which run in the browser.
- Server component client: This is used to access Supabase from Server Components, Server Actions, and Route Handlers, which run exclusively on the server.
We will create the utility files, a recommended way, and set up these clients. We will organize them within the utils/supabase
directory at the root of our project. For our case we will go with the Client component client.
In the client.ts
paste the following code:
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
Note: if in your project you can't find the ssr
module, please check this to install.
Setting up Supabase storage
First, we need to create a storage bucket in Supabase. We will name our bucket image_upload_kodaschool
but you can name it whatever you want. You will have to choose to make it public if you like.
You can also play around, make various adjustments such as setting permissions, creating policies, and making the bucket public.
As shown in the image above, you can upload images manually by dragging and dropping them into the bucket. You can organize your storage by creating different folders. This makes it easy to categorize and manage your files.
Create an Input File and Image Component
To this point we’re all set up for development, we’ve already set everything we need to keep going, doing the most fundamental with Supabase storage. We will have to create an input field for file uploads and an image component, to display the uploaded image.
"use client";
import Image from "next/image";
import { useState } from "react";
export default function Home() {
const [imageUrl, setImageUrl] = useState<string>("");
const uploadImage = async (e: React.ChangeEvent<HTMLInputElement>) => {};
return (
<main className="flex min-h-screen flex-col items-center bg-gray-800 text-gray-200 p-24">
<h1 className="text-5xl font-bold">NextJs & Supabase Storage</h1>
<div className="mt-5">
<input
type="file"
onChange={uploadImage}
className="block w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 cursor-pointer dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
/>
</div>
<div>
{imageUrl && (
<Image
src={imageUrl}
alt="Uploaded Image"
width={300}
height={300}
className="rounded-lg border border-gray-300"
/>
)}
</div>
</main>
);
}
The above code is a simple template to start up, it contains an input file element, and an image component, which we will display the uploaded image.
We’ve kept the state to manage the state of the image URL, and when we fetch the image from the bucket, we will then use it to set the image url and display it in the image.
Note: defining policies are important, you should keep in mind to write the policies to allow who can perform some actions in your bucket, e.g. authenticated users can add, update, delete and so on.
Head to the policies to define the policies. We will set it to true
in our case to allow everyone to demo along. That will allow everyone to do some actions on the images.
Uploading image
const uploadImage = async (e: React.ChangeEvent<HTMLInputElement>) => {
try {
if (!e.target.files || e.target.files.length === 0) {
throw new Error("You must select an image to upload.");
}
const file = e.target.files[0];
const { data: image, error: uploadError } = await supabase.storage .from("image_upload_kodaschool")
.upload(`${file.name}`, file);
if (uploadError) {
throw uploadError;
}
if (image) {
console.log(image);
}
} catch (error) {
console.log(error);
}
};
In the uploadImage
function, we handle the Supabase upload, uploading the image. We check if the file exists, once we have confirmed that a file is selected, we extract the first file from the file input. This file object contains all the necessary information about the selected image, such as its name and content.
image_upload_kodaschool
is the name of the bucket where we store our images
, and ${file.name}
specifies the file name under which the image will be saved.
If an error occurs during the upload process, it is thrown and caught in the catch block, where it is logged to the console, and if everything goes well, the image will be uploaded successfully to the bucket.
When we upload, you can see the image uploaded successfully, and to view it you can click on the file and view it on the right. You can also define the folder to structure the files categories.
Retrieving image & displaying it
After upload we can retrieve the public URL of the uploaded image. Fortunately, supabase has a convenient `getPublicUrl` method to get the URL that we can use to access it publicly.
const { data: imgUrl} = await supabase.storage
.from("image_upload_kodaschool")
.getPublicUrl(`${file.name}` ?? "default");
if (imgUrl) {
setImageUrl(imgUrl.publicUrl);
}
In the code above, we can call the getPublicUrl
on the same storage bucket, and we pass in the file name. After the image is successfully retrieved, we set in the imageUrl
state, triggering a re-render of our component to display the uploaded image.
Our final web app looks like this.
The uploadImage
function handles both the upload and retrieval of the image URL using Supabase.
Code practice
You can find complete code here, practice with it.
What to Take Note Of
- Set Permissions According to Your Needs: Ensure your Supabase bucket permissions are configured correctly based on your requirements, following the documentation for setting policies.
- Configure the Folder Structure: Organize your project with the recommended folder structure to maintain a clean and manageable codebase.
- Validate File Types and Sizes: Implement validation to check file types and sizes before uploading to prevent unsupported or large files.
- Optimize Image Loading: Use Next.js’s Image component features like lazy loading and responsive images to optimize performance.
Wrap Up
In this post, we learned how to use Supabase Storage to upload and display images in a Next.js application. Supabase provides an easy-to-use and secure way to handle file storage. Try integrating it into your own projects and see how it can simplify your file management needs.