Lemon Squeezy x Supabase x Next JS - Part 4
Create the billing page and render the Pricing Dialog
The complete guide is a work in progress, this fourth part is completed.
This is part four of the guide, for the full guide, start here.
If you are looking for a guide to help you out with setting up your very own Subscription using Next.JS, Lemon Squeezy and Supabase, you have come to the right place. In this first guide, we will look at how you can fetch products from Lemon Squeezy, so that you can populate your pricing table!
This guide is specifically created for Subscriptions with one product and multiple variants. You can see the difference in pricing strategy here.
The goal of this guide is that by the end you will be able to fetch your own products using Next.js 13 App Router.
The pricing page
The original page I used has been cut, as this is specific for my needs. Instead what remains is the most important part to make the guide work.
On top of that, we will finally start seeing some Supabase action!
Let's look at the completed page. At this point, all we have is a centered Button, which opens a dialog.
I specifically created it this way, so you have complete control of your styling, additional page information and layout.
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import Pricing from "./pricing";
export default async function Home() {
const supabase = createServerComponentClient<Database>({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
redirect("/login");
}
const { data: companyId } = await supabase
.from("user_info")
.select("company_id")
.eq("id", session?.user.id)
.single();
const { data: company } = await supabase
.from("company_info")
.select()
.eq("company_id", companyId?.company_id)
.single();
return (
<div className="m-auto">
<Pricing company_id={company.company_id} user={session} />
</div>
);
}
Deep dive into page.tsx
Before we go into more detail, the rest of this part of the guide will be internal linking between different components, how the parameters are linked and why we can't retrieve all information directly.
Imports
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import Pricing from "./pricing";
createServerComponentClient
is a function that creates a client that can be used to access the Supabase API.cookies
is a function that returns the cookies that are set on the request.redirect
is a function that redirects the user to a new page.Pricing
is a component that displays the pricing page.
The Page.tsx function
export default async function Home() {
// additional content
}
As we are creating a server component, we are using the export default async function statement. This way we make sure we can also include server components in our page, and this page will be server static generated. Making it possible to prepopulate and be used for ranking in Google!
Supabase Client and Session
const supabase = createServerComponentClient<Database>({ cookies });
const {
data: { session },
} = await supabase.auth.getSession();
if (!session) {
redirect("/login");
}
createServerComponentClient
The createServerComponentClient
is a helper that creates an instance of the Supabase client for use in server components, which are used to retrieve the data from the server. We pass the function the object of cookies, which we get from next/headers in next.js app router. This way we can store the session data of the user, and we can retrieve the information based on this session.
supabase.auth.getSession()
This function calls Supabase to check the session object. This information contains information about the user, such as their email, the session id and their JWT token.
Check if the user is logged in
- We check if the user is logged in.
- If the user is not logged in, we redirect the user to the login page.
Getting the company ID based on the user
const { data: companyId } = await supabase
.from("user_info")
.select("company_id")
.eq("id", session?.user.id)
.single();
We get the company ID from the user_info table, by executing this query. Because we run a server page, this function is directly called. Because we can run multiple requests directly, it is advised to change the { data }
object to a specific name, using the colon then typing the name. In this example we renamed it to companyId.
- We select the company ID from the user info table
- We filter the user info table by the user ID we retrieve from the session
Getting the company info
const { data: company } = await supabase
.from("company_info")
.select()
.eq("company_id", companyId?.company_id)
.single();
As we have retrieved the company id of the user, we can now alos request the company_info information. In this table I have stored the subscription id, the product id, expiration date of the subscription and much more Lemon Squeezy info inside of Supabase.
With this information available, we can check if the company of this user already has an active subscription.
In this guide we are not using this information, so for now we skip this part.
Returning the Dialog content with the proper values
After the content has been retrieved, we can now render the pricing component. This pricing component is ofcourse linked to the other components. So we create the final step into creating the pricing dialog and sending the user to the correct store variant.
return (
<div className="mx-auto">
<Pricing company_id={company.company_id} user={session} />
</div>
);
The dialog trigger (a button) will be displayed on screen.
And if you click on it a functional pricing dialog is shown!
What is even better, if you then click on Buy now, you will be directed to the Checkout page of your Lemon Squeezy store. How cool is that?
The Next.js component links
To render the information, we have created 3 components and one page.
Now that we have the first setup completed, let's look how they are related and they make it possible for you to render the data.
As we saw before, we have a combination of three components and one page. Out of these components, we have:
- 1 Client component
- 2 Server components
Let's look top down.
- page.tsx
On page.tsx, we get the data from Supabase. First we get the session, with the user id from the session we get the company id, and with the company id we get the billing information of the company. This information is then passed to the pricing component, which is rendered on the page.
- pricing.tsx
The pricing component, calls the function getProductVariants with the product id that is stored in the environment variables. This component imports two other components:
- getProductVariants function from
./variants.tsx
- PricingDialog from
./pricing-dialog.tsx
The pricing-dialog.tsx component is a client component, as the dialog component from shadcn ui is not usable as a server component.
- variants.tsx
This page fetches our Lemon Squeezy product variants. Which can later be called by a different component (pricing.tsx) to populate the other component.
- pricing-dialog.tsx
As mentioned, the "use client"
component that renders the data for us. This component receives all parameters, to be able to render all the information and generate a checkout link based on the rendered variant from Lemon Squeezy:
- productVariants
- user
- company_id
- store_id
The good news: Users can pay you now
Great, users are able to pay you. However, you haven't registered this in your system. Now they can pay, but they can't use..
Let's fix that in the next part.
Like what you see? Do you prefer to have a ready made app instead of building the integration yourself?
No worries, we have got you covered.
With Supaboost, you will be able to skip at least 30 days development time, by having these features readily available:
- Auth
- Lemon Squeezy integration
- Safe development with Typescript
- Next.js App Router
- Supabase SQL scripts
- And much more.