Fetching image from Supabase storage through database table returns undefined URL

3.6k Views Asked by At

I am building an app in Next.js, which fetches dynamic data from a Supabase table. The table (called product) has several data points (title, description, image). My table in Supabase looks like this: enter image description here

My problem is that both the description and the title are being pulled in dynamically, populating my home page properly. What is failing is the image. Images are stored in a public bucket that looks like this:

enter image description here

The way I'm attempting to pull the image in dynamically is as follows:

import { supabase } from "../utils/supabase";
import Link from "next/link";
import { useUser } from "../context/user";
import Image from "next/dist/client/image";

export default function Home({ products, tile_url }) {
  const { user } = useUser();

  const {data:image_url} = supabase.storage.from("games").getPublicUrl(tile_url);
  console.log(image_url.publicURL);

  return (
    <div className="body w-full h-screen py-16">
      <div className="w-full max-w-3xl mx-auto px-2">
        {products.map((product) => (
          <Link
            key={product.id}
            href={`/${product.id}`}
          >
            <a className="p-8 h-40 mb-4 rounded element text-xl flex">
             
              <img src={image_url.publicURL} alt="" />
              {product.title}
            </a>
          </Link>
        ))}
      </div>
    </div>
  );
}

export const getStaticProps = async () => {
  const { data: products } = await supabase.from("product").select("*");

  return {
    props: {
      products,
    },
  };
};

The image is not returned in the frontend. The console.log returns the url, but instead of the image name, it pastes undefined a the end:

https://[project_identifier].supabase.co/storage/v1/object/public/games/undefined

The expected outcome would be:

https://[project_identifier].supabase.co/storage/v1/object/public/games/gameOneGameTile.jpeg

Any ideas as to what I am doing wrong? Thank you in advance!

EDIT:

Based on a response from @dshukertjr on this question, I have included the path to the image in the table, to be able to use the column name to fetch the data. However, nothing has changed. enter image description here

3

There are 3 best solutions below

2
On BEST ANSWER

What you need to pass to the getPublicUrl() function is the path to the image within the bucket for example like this:

  const pathToImage = 'game_one/gameOneGameTile.jpeg'

  const {data:image_url} = supabase.storage.from("games").getPublicUrl(pathToImage);

You are passing tile_url in your code. If you want to keep it that way, the path to the image needs to be saved in your product table under tile_url column for each row to be able to display an image.

0
On

This answer is top of Google for this question and keywords and for rookies like me, here is a complete process on how you can upload and display image files in supabase buckets

  1. Once you have authenticated a user with supabase, you need 3 things to upload an image to a bucket:
    1. the file
    2. a bucket name
    3. a file path
  2. When you upload that image you need to store the file path in your DB. The file path is how you reference the image later to delete, edit etc.
  3. To display the image, you use that store file path to get a publicUrl to the image which you can use to display it. You can't (well shouldn't) just use your project name, and bucket name, and file path to directly construct a URL. There might be policies that prevent access, or supabase could change their file structure. Using the publicUrl function means the image url will always work, and won't randomly break in the future.
  4. The getPublicUrl function requires two things, bucket name, and file path

Details

File paths consist of the folder structure, and the name of the file. File paths are not URLs.

A file path could be something like

filePath =  images/45.png

images is the folder I want to put the file in. 45.png is the name of the file.

So an upload function could look like this:

const handleUpload = async (event) => {
    event.preventDefault(); //stop form from reloading page  
    
    const file = event.target.files[0]; //get the file from the file picker
    if (!file) return;
    const fileExt = file.name.split('.').pop(); //get the file type
    const fileName = `${Math.random()}.${fileExt}`; //create a name
    const filePath = `images/${fileName}`; //create the file path

    try {
        let { error } = await supabase.storage
            .from(bucketName)
            .upload(filePath, file);

        if (error) {
            throw error;
        }
        console.log('File uploaded successfully: ', filePath);
        //this is where you would store the filePath in your DB
    } catch (error) {
        console.error('Error uploading the file', error);
    } finally {
        getFilePath(filePath); //use the filePath to get the publicUrl
    }
};

Then the function to get the public URL, what you will actually use to put the image on your page could look like this:

async function getFilePath(filePath) {
    const { data, urlError } = await supabase
        .storage
        .from(bucketName)
        .getPublicUrl(filePath)

    if (urlError) {
        throw urlError;
    }
    console.log('got public url', data)
    setImageUrl(data.publicUrl); //make sure to pull the URL out of the object
}
0
On

For working with private storages, I had to create a signed url that I could use as an image src. There aren't a lot of good alternatives unfortunately, as there isn't a way to pass in the authorizer to an image src.