Paddle Next.js Starter Kit

repository·main·Indexed 43 days ago

https://github.com/paddlehq/paddle-nextjs-starter-kit

A production-ready starter kit for building SaaS applications using Next.js, Supabase, and Paddle Billing. It automates subscription management, webhooks, and localized pricing for 200+ markets. Features include integrated Paddle Checkout, user management via Supabase, and a pre-configured UI built with shadcn/ui and Tailwind CSS. Version 0.1.0.

Tokens
5.7K
Snippets
21
Records
29
Agent score
56%

What's inside Paddle Next.js Starter Kit

  1. Overview of Paddle Billing Next.js Starter Kit

    main

    The paddle-nextjs-starter-kit is a complete template for building and deploying SaaS applications using Next.js and Paddle Billing. It provides a pre-configured stack to handle subscriptions, payments, and user management.

    Key Features

    • Localized Pricing: A three-tier pricing page localized for 200+ markets using Paddle.js.
    • Integrated Checkout: Uses Paddle Checkout for secure payments (Card, Apple Pay, Google Pay, PayPal, etc.).
    • User Management: Integrated with Supabase for authentication and user data.
    • Subscription Management: Ready-made UI screens for customers to manage payments and subscriptions.
    • Data Syncing: Automatic synchronization of customer and subscription data via Paddle webhooks.

    Technology Stack

    • Framework: Next.js
    • Auth/User Management: Supabase
    • UI Components: shadcn/ui
    • Styling: Tailwind CSS
    • Billing: Paddle Billing (Note: Does not support Paddle Classic)
  2. Authorize your Domain in Paddle

    main

    To prevent unauthorized sellers, you must approve your Vercel deployment URL within the Paddle dashboard before checkouts will work.

    1. Website Approval: Go to Paddle > Checkout > Website approval, enter your Vercel app URL, and click Submit for approval.
    2. Default Payment Link: Go to Paddle > Checkout > Checkout settings, enter your Vercel app URL as the default payment link, and click Save.
    3. Webhook Verification: Go to Paddle > Developer tools > Notifications and ensure the endpoint URL matches your Vercel domain (e.g., https://your-app.vercel.app/api/webhook).
  3. Deploy the Starter Kit to Vercel

    main

    You can deploy the project to Vercel by cloning the repository directly through the Vercel deployment button or by creating a new application manually.

    During deployment, you must configure the following environment variables to connect your Paddle and Supabase accounts.

    https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FPaddleHQ%2Fpaddle-nextjs-starter-kit&env=PADDLE_API_KEY,PADDLE_NOTIFICATION_WEBHOOK_SECRET,NEXT_PUBLIC_PADDLE_ENV,NEXT_PUBLIC_PADDLE_CLIENT_TOKEN&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6&external-id=https%3A%2F%2Fgithub.com%2FPaddleHQ%2Fpaddle-nextjs-starter-kit%2Ftree%2Fmain
  4. Set up Product Catalog and Pricing

    main

    After deploying, you need to define your products and prices in Paddle and link them to the application.

    1. Create Products in Paddle

    Create products and prices in your Paddle dashboard. A common pattern is to create three products (one for each tier) with two prices each (e.g., 'monthly' and 'annual').

    2. Update Local Constants

    Copy your new Price IDs and update the constants in the following file: src/constants/pricing-tier.ts

    3. Sync Changes

    Commit and push your changes to the main branch to update the deployed application.

  5. External Resources for Paddle and Next.js

    main

    For deeper technical implementation and reference, use the following resources:

    • Build and deploy Next.js app with Vercel and Supabase: A guide for integrating the stack used in this starter kit.
    • Paddle API reference: The official documentation for all Paddle Billing API endpoints and methods.
    • Sign up for Paddle Billing: Link to create a Paddle account to begin using the service.
  6. Prerequisites for Local Development

    main

    Before setting up the starter kit, ensure your environment meets the following requirements:

    Local Environment

    • Node.js: Version > 20
    • Package Manager: npm, Yarn, or pnpm

    Required Accounts

    • Vercel account (for deployment)
    • Supabase account (for auth and database)
    • Paddle Billing account (Sandbox account is recommended for testing)
  7. Configure Paddle Environment Variables

    main

    The following environment variables are required for the starter kit to function correctly. You can retrieve these from your Paddle Dashboard under Developer tools.

    VariablePurposeHow to get it
    PADDLE_API_KEYBackend API key for interacting with Paddle data (e.g., syncing with Supabase).Paddle > Developer tools > Authentication (Create a new API key)
    NEXT_PUBLIC_PADDLE_CLIENT_TOKENFrontend client-side key for Paddle.js (e.g., localized prices, opening checkout).Paddle > Developer tools > Authentication (Create a new client-side token)
    PADDLE_NOTIFICATION_WEBHOOK_SECRETSecret used to verify that incoming webhooks are authentic and untampered.Paddle > Developer tools > Notifications (Create a destination for https://<PROJECTNAME>.vercel.app/api/webhook and copy the secret)
    NEXT_PUBLIC_PADDLE_ENVSpecifies the Paddle environment.Enter sandbox for sandbox accounts or production for live accounts.
  8. Test the Checkout Flow with Sandbox Test Cards

    main

    Once configured, verify the integration by launching a checkout from your pricing page. If using a Paddle Sandbox account, use the following test card details:

    FieldValue
    Email addressAny email address you own
    CountryAny valid country supported by Paddle
    ZIP codeAny valid ZIP or postal code
    Card number4242 4242 4242 4242
    Name on cardAny name
    Expiration dateAny valid future date
    Security code100
  9. Configure Next.js middleware matcher

    main

    The middleware in this project uses a config object with a matcher array to define which request paths should trigger the middleware execution. The default configuration is designed to run on all paths except for static assets, optimized images, the favicon, and common image file extensions (svg, png, jpg, jpeg, gif, webp). This prevents unnecessary session updates on static file requests.

    export const config = {
      matcher: [
        /*
         * Match all request paths except for the ones starting with:
         * - _next/static (static files)
         * - _next/image (image optimization files)
         * - favicon.ico (favicon file)
         * Feel free to modify this pattern to include more paths.
         */
        '/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
      ],
    };
  10. Sign in with GitHub OAuth

    main

    Use the signInWithGithub server action to initiate a GitHub OAuth flow. This action uses Supabase Auth to redirect the user to GitHub for authentication. The callback URL is hardcoded to https://paddle-billing.vercel.app/auth/callback. Note that this action performs a redirect internally.

    // Call the action (it handles the redirect internally)
    await signInWithGithub();
  11. Validate a user session with `validateUserSession()`

    main

    Use validateUserSession() to ensure a user is authenticated before performing sensitive server-side operations.

    This function:

    1. Initializes a Supabase client via createClient().
    2. Attempts to retrieve the current session via supabase.auth.getSession().
    3. Throws an error with the message 'You are not allowed to perform this action.' if no session is found.

    If the function completes without throwing, the user is considered authenticated.

    import { validateUserSession } from '@/utils/supabase/server';
    
    export async function MyServerAction() {
      try {
        await validateUserSession();
        // Proceed with protected logic
      } catch (error) {
        // Handle unauthorized access
        console.error(error.message);
      }
    }
  12. Get default error state with getErrorMessage()

    main

    Use getErrorMessage to retrieve a standardized error object. This is useful for populating UI error states when a Paddle operation fails.

    It returns an object with the following structure:

    • error: The string 'Something went wrong, please try again later'
    • data: An empty array []
    • hasMore: false
    • totalRecords: 0
    import { getErrorMessage } from '@/utils/paddle/data-helpers';
    
    const errorState = getErrorMessage();
    // Returns: { error: 'Something went wrong, please try again later', data: [], hasMore: false, totalRecords: 0 }