Next.js, Supabase & Tailwind CSS Image Gallery

Banner for a MediaJam post

Eugene Musebe

Introduction

This Mediajam will show you how to use Next.js to retrieve Cloudinary images from a PostgreSQL database (Supabase) and provide optimized, blurred images with next/image.

Getting Started

We can use create-next-app to clone a Next.js and Tailwind CSS starter application ;

1npx create-next-app --example cloudinary-supabase

The command above will start a Nextjs application using the most recent Next.js version. Follow the steps in the Tailwind Documentation to use Tailwind CSS.

After setting up Tailwind, Create a Components folder inside the pages directory and create a BlurImage.js component in it. This component will be used to render individual images and their metadata.

In the component, we will use Next's image component to achieve good Core web vitals and optimize the images.

For a better user experience, we'd like to show a blurred version of images while they load. Even though this is part of the Image component for local image files, we will soon be getting images from Supabase. Instead, we can get a similar effect by using onLoadingComplete, CSS animation, and blur as illustrated below :

1import Image from 'next/image';
2import { useState } from 'react';
3
4function cn(...classes) {
5return classes.filter(Boolean).join(' ');
6}
7
8const BlurImage = ({ img }) => {
9const [isLoading, setLoading] = useState(true);
10
11return (
12<a href={img.href} className='group'>
13<div className='aspect-w-1 aspect-h-1 w-full overflow-hidden rounded-lg bg-gray-200 xl:aspect-w-7 xl:aspect-h-8'>
14
15<Image
16alt=''
17src={img.imageSrc}
18layout='fill'
19objectFit='cover'
20className={cn(
21'group-hover:opacity-75 duration-700 ease-in-out',
22isLoading? 'grayscale blur-2xl scale-110':'grayscale-0 blur-0 scale-100'
23)}
24onLoadingComplete={() => setLoading(false)}
25/>
26
27</div>
28<h3 className='mt-4 text-sm text-gray-700'>{img.name}</h3>
29<p className='mt-1 text-lg font-medium text-gray-900'>{img.username}</p>
30</a>
31);};
32
33export default BlurImage;

The Next step is to Create a Gallery.js component that will be used to style and display all the images we shall fetch from supabase. Remember the BlurImage component is used to style a single image while the gallery component will be used to display and format all the images from supabase ;

1import BlurImage from './BlurImage';
2
3const Gallery = ({ img }) => {
4
5return (
6<div className='mx-auto max-w-2xl py-16 px-4 sm:py-24 sm:px-6 lg:max-w-7xl lg:px-8'>
7<div className='grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8'>
8<BlurImage key={img.id} img={img} />
9</div>
10</div>
11
12)};
13export default Gallery;

In the above code, we first import the BlurImage.js component then we style it using Tailwind and CSS grid to display the images in a grid layout.

The Next.js Image component needs an allowlist to tell it which domains' images we can optimize. While we're using res.cloudinary.com as a placeholder, we can add any domain we want to the allowlist in next.config.js:

1/** @type {import('next').NextConfig} */
2
3module.exports = {
4images: {
5reactStrictMode: true,
6domains: ['res.cloudinary.com'],
7},
8target: 'serverless',
9};

Getting Supabase Up and Running

Supabase makes it incredibly simple to build your backend, providing you with a PostgreSQL database in just a few clicks. Let's configure our new project in our Supabase dashboard:

Sign up to Supabase - https://app.supabase.com and create a new project. Wait for your database to start then ;

  • Click "New project"
  • Choose your organization
  • Pick a name, database password, and click "Create new project"
  • After creating your project, you will need to copy and save these two values:
  • Project API keys → service_role: This is how we'll securely connect to Supabase on the server.
  • Configuration → URL: This is the API endpoint the Supabase client will use to fetch and edit data ;

https://res.cloudinary.com/hackit-africa/image/upload/v1654685849/supa.png

Save these values as Environment Variables. Inside your Next.js application, use env.local as follows:

1NEXT_PUBLIC_SUPABASE_URL=your-value-here
2SUPABASE_SERVICE_ROLE_KEY=your-value-here

Adding Data to Supabase

First, we will need to create a new table inside Supabase for our images.

  • We'll name our table images
  • We'll keep the id and created_at columns as is
  • Let's add name, href, userName, and imageSrc columns, which are all text
  • Click "Save" and we're done!

Using the Supabase client insert data in the created table using the following command :

1await supabaseAdmin.from('images').insert([{
2 name: 'Eugene Musebe',
3 href: 'https://twitter.com/_musebe',
4 userName: '_musebe',
5 imageSrc: 'https://res.cloudinary.com/hackit-africa/image/upload/v1580219806/me.jpg'

Fetching Data from Supabase

We've already configured our Supabase client in the file to allow us to select all photos from our images table: The action of fetching data from supabase will be performed on the index.js file then we shall pass the data to our BlurImages.js and Gallery.js components as props

Firts we need to configure supabase into our application with the following command :

1npm i @supabase/supabase-js

The next step is to create a new supabase client that will enable the application communicate with the Supabase infrastructure :

1import { createClient } from '@supabase/supabase-js';

We'll retrieve this information using getStaticProps. This function allows you to fetch data from the server and return it as props for the Page: default React component.

1export async function getStaticProps() {
2const supabaseAdmin = createClient(
3process.env.NEXT_PUBLIC_SUPABASE_URL || '',
4process.env.SUPABASE_SERVICE_ROLE_KEY || ''
5);
6const { data } = await supabaseAdmin.from('images').select('*').order('id');
7console.log(data);
8
9return {
10 props: {
11 images: data,
12},};
13}

Once the data is fetched we need to pass it to the main component as follows :

1export default function Home({ images }) {
2
3return (
4
5<Layout>
6{images.map((img) => (
7<Gallery key={img.id} img={img} />
8))}
9</Layout>
10
11);
12
13}

And we are done !!! Once all the above has been done your supabase-cloudinary gallery should be able to look like this :

![](Attach Screenshot of the final application)

Conclusion

We were able to develop a Next.js application that displayed a list of photographs dynamically downloaded from Supabase and Cloudinary.

Codesandbox

The final project can be viewed on Codesandbox.

You can find the complete source code on my Github repository.

Eugene Musebe

Software Developer

I’m a full-stack software developer, content creator, and tech community builder based in Nairobi, Kenya. I am addicted to learning new technologies and loves working with like-minded people.