Supabase, Next.js and Builder.io

Supabase, Next.js and Builder.io

We will walk through how to get up and running on Next.js with Supabase. Then we will build UI using Builder.io.

Original: codingcat.dev/tutorial/supabase-nextjs-and-..

What are similar tools to Supabase that you may have used?

  • Firebase - Most are trying to match features of Firebase. Offers a realtime JSON database and a document based database called Firestore. Closed source available on Google Cloud Platform.

  • Apprite - Allows for a Docker image to be hosted anywhere that you want, with a MariaDB database.

  • Nhost - Postgres Database hosted on Hasura.

Getting Started with Supabase

Once you sign in to Supabase’s dashboard you will be presented with the option to create a new Project ( you may also need to create an organization).

Live Coding

GitHub Repo - Final project that you can run locally, just make sure to add your own .env files.

Jon and I talk about how to setup Supabase for Authentication first. We talk about the Magic Link system where you can open a url in your email to login. We then dive into the code.

Connection

You must get your API keys from /project/[yourproject]/settings/api and add them to .env.

You will need to make sure you have a supabase instance created from your configuration to connect the client side of your application.

/utils/supabaseClient.js

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

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey)

Authentication

How to easily check for authenticated user and show a react component.

/pages/sports.js

import Sports from '../components/Sports';
import { useState, useEffect } from 'react';
import { supabase } from '../utils/supabaseClient';

export default function Home() {
  const [session, setSession] = useState(null);
  const [me, setMe] = useState(null);
  useEffect(() => {
    setSession(supabase.auth.session());

    supabase.auth.onAuthStateChange((_event, session) => {
      setSession(session);
    });
  }, []);
  return (
    <>
      <div className="container">
        {session ? <Sports session={session} /> : <div>Please Login</div>}
      </div>
    </>
  );
}

Data

In Supabase you can see data in your Postgres database.

Supabase uses PostgREST to then allow you to access this table.

Access Data in the Client

This is our full sports page that brings in all of our sports table. There are a couple things to call out here.

  • You can add sports by just calling supabase.from('sports').insert

  • When you setup a listener you get the full payload supabase.from('sports').on('*', (payload)

import { supabase } from '../utils/supabaseClient';
import { faker } from '@faker-js/faker';
import { useEffect, useState } from 'react';
import Image from 'next/image';

export default function Sports({ session }) {
  const [sports, setSports] = useState([]);
  useEffect(() => {
    const sub = supabase
      .from('sports')
      .on('*', (payload) => {
        setSports((current) => [payload.new, ...current]);
        // supabase
        //   .from('sports')
        //   .select('*')
        //   .order('created_at', { ascending: false })
        //   .then((d) => console.log(d));
      })
      .subscribe();
    supabase
      .from('sports')
      .select('*')
      .order('created_at', { ascending: false })
      .then((d) => setSports(d.data));
    return () => {
      if (sub) sub.unsubscribe();
    };
  }, []);

  const addSports = async () => {
    const name = faker.name.findName();
    const image = faker.image.sports(null, null, true);
    await supabase.from('sports').insert([{ name, image }]);
  };
  return (
    <div>
      {session && (
        <div>
          <button onClick={() => addSports()}>Add Sports</button>
          <ul
            style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr' }}
          >
            {sports?.map((sport) => (
              <li
                key={sport.id}
                style={{ display: 'flex', flexDirection: 'column' }}
              >
                {sport.image && (
                  <div>
                    <Image
                      loader={(image) => image.src}
                      src={sport.image}
                      width="100px"
                      height="100px"
                      alt={sport.name}
                    />
                  </div>
                )}
                <div>{sport.name}</div>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

Policies

In order to protect your data you need to setup Policies. These are pretty awesome because they can be written at any level within your table.

Builder.io

You can access Builder.io’s Dashboard. If you hit (CMD or CTRL) + P, and type in API this will give you your API key.

In Next.js getStaticProps you can get all the data for your page. This is where we willl look for pages that are created in builder. Pass that data to the client.

const page =
    (await builder
      .get('page', {
        apiKey: 'e89fbe16533145d4ae320467ed536a91',
        userAttributes: isPersonalizedRequest
          ? {
              // if it's a personalized page let's fetch it:
              ...getTargetingValues(params.path[0].split(';').slice(1)),
            }
          : {
              urlPath: '/' + (params?.path?.join('/') || ''),
            },
        cachebust: true,
      })
      .toPromise()) || null;

Get our sports data from Supabase and pass to client.

const { data: sports } = await supabase
    .from('sports')
    .select('*')
    .order('created_at', { ascending: false });

Then display both our page and data using a single component.

<BuilderComponent
        renderLink={Link}
        model="page"
        content={page}
        data={{ sports }}
      />

That is all it takes to hook up Builder.io and start interacting with the data from Supabase using the state.

Did you find this article valuable?

Support Alex Patterson by becoming a sponsor. Any amount is appreciated!