This Next.js demo app can be used as a base for building subscription-based SaaS apps.
Just clone this repo and build your app alongside the ready-made auth and billing.
Using the following stack:
- Framework - Next.js 14
- Language - TypeScript
- Billing - Lemon Squeezy
- Auth (GitHub OAuth) - Auth.js v5
- ORM - Drizzle
- Styling - Tailwind CSS
- Components - Wedges
- Serverless Postgres - Neon
- Linting - ESLint
- Formatting - Prettier
This template uses the Next.js App Router. This includes support for enhanced layouts, colocation of components, tests, and styles, component-level data fetching, and more.
Compatbile with Vercel Edge Functions and serverless deployments.
Keep in mind that Lemon Squeezy comes with inbuilt Customer Portal that covers all the features from this app and more.
Nonetheless, should you seek a billing solution more closely integrated with your SaaS platform, this template serves as a foundation for creating a seamless, integrated SaaS billing system.
You need a Lemon Squeezy account and store. If you don't have one already, sign up at Lemon Squeezy.
This template uses Neon + Drizzle ORM for serverless Postgres, making it compatible with the Vercel Edge functions. If you don't have an account, you can sign up for free at Neon.
Start by cloning this repo to your local machine and navigating into the directory.
Then, install the project dependencies:
pnpm install
Copy the .env.example
file to .env
:
cp .env.example .env
Then, fill in the environment variables:
LEMONSQUEEZY_API_KEY=
LEMONSQUEEZY_STORE_ID=
LEMONSQUEEZY_WEBHOOK_SECRET=
WEBHOOK_URL=
POSTGRES_URL=
AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=
AUTH_SECRET=
AUTH_URL=
NEXT_PUBLIC_APP_URL=
Once you have created an account and store on Lemon Squeezy, make sure you're in Test mode, then go to Settings > API and create a new API key. Copy the key and paste it into .env
file where it says LEMONSQUEEZY_API_KEY=
.
You will also need the store ID from Lemon Squeezy for LEMONSQUEEZY_STORE_ID
, which you can find in the list at Settings > Stores.
Finally, you will need to add a random webhook secret in LEMONSQUEEZY_WEBHOOK_SECRET
. A webhook secret is a security key that ensures data received from a webhook is genuine and unaltered, safeguarding against unauthorized access.
Your local app will need to be able to receive webhook events, which means creating a web-accessible URL for your development project.
This is not available when running your site on its local server without some sort of tunnel.
An easy way to set one up is using a service like ngrok or an app like LocalCan.
Once you are provided a URL by these services, simply add that in your .env
file where it says WEBHOOK_URL=
.
You can get the Postgres URL from your Neon account. For more information refer to the Neon documentation.
You will need to set up a GitHub OAuth app in order to obtain GITHUB_SECRET
and GITHUB_ID
to handle authentication.
Refer to the GitHub documentation for setting up GitHub OAuth.
Once you have set up the OAuth app, you will need to add the AUTH_GITHUB_SECRET
and AUTH_GITHUB_ID
to your .env
file.
Additionally, you need to add a random secret for AUTH_SECRET
in your .env
file. On Linux or macOS, you can generate a random secret using the following command:
openssl rand -hex 32
or go to https://generate-secret.now.sh/32 to generate a random secret.
Next, you need to provide the URL of your app in AUTH_URL
in format https://your-app-url.com/api/auth
. For local development, you can use http://localhost:3000/api/auth
.
Finally, you will need to add the URL of your app in NEXT_PUBLIC_APP_URL
. For example, http://localhost:3000
.
Open http://localhost:3000 with your browser to see the result.
Run the following command to set up the database:
pnpm db:push
With Drizzle ORM, you can access the database with Drizzle Studio. Run the following command to open Drizzle Studio:
pnpm db:studio
Go to https://local.drizzle.studio/ to access the database.
Start the development server:
pnpm dev
That's all, you're ready to go!
This is a required step.
For your app to receive data from Lemon Squeezy, you need to set up webhooks in your Lemon Squeezy store at Settings > Webhooks.
In the app we have provided an action (Setup webhook button) that demonstrates how you can create a webhook on Lemon Squeezy using the Lemon Squeezy SDK.
When you create a webhook, you should check at least these two events:
subscription_created
subscription_updated
This app demo only processes these two events, but they are enough to get a billing system in place. You could, for example, extend the app to handle successful payment events to list invoices in your billing system (by subscribing to subscription_payment_success
).
The webhook endpoint in your app is /api/webhook
, which means if you are manually setting up the webhook, you need to append /api/webook
to your webhook URL on Lemon Squeezy. For example, https://your-app-url.com/api/webhook
The server action for creating a webhook via SDK will do that automatically for you.
In the webhook form you need to add a signing secret. Add the same value you use in the form in the LEMONSQUEEZY_WEBHOOK_SECRET
environment variable.
There are a few things to update in your code to go live.
You need to turn off the Test mode in your Lemon Squeezy store and add a new live mode API key. Add this API key as an environment variable in your live server, using the same name LEMONSQUEEZY_API_KEY
. Your store ID remains the same in both test and live mode, so add that to your server environment variables, as you did for your development site.
You also need to create a new webhook in your live store. Make sure you add the signing secret into the LEMONSQUEEZY_WEBHOOK_SECRET
variable on your server.