Saasy soup logo

Saasy Soup

Building you own Landing Page with Waitlist using Nextjs, Supabase & ShadeCN

Cover Image for Building you own Landing Page with Waitlist using Nextjs, Supabase & ShadeCN

Landing pages are your digital first impression ā€“ they're the difference between a visitor who bounces and one who becomes an early adopter. In today's fast-paced startup environment, you need a landing page that not only looks professional but actively works to convert visitors into customers. That's where LaunchBase comes in.

Unlike other boilerplates that focus solely on design or basic functionality, LaunchBase delivers a complete, production-ready solution. It combines a beautiful, conversion-optimized design with powerful features like waitlist management, built-in analytics, and SEO best practices ā€“ all using a modern tech stack (Next.js 14, Sequelize, shadcn/ui) that scales with your success. Whether you're a solo founder or part of a startup team, LaunchBase lets you skip weeks of setup and get straight to what matters: collecting emails, understanding your audience through analytics, and launching your product faster. Plus, with TypeScript support and a robust ORM, you're building on a foundation that can grow from 100 to 100,000 users without missing a beat.

If you just interested in code, go ahead on LaunchBase site and get the full code repository and use directly.

In this tutorial, we'll create a sleek landing page with waitlist functionality using Next.js, Sequelize ORM for database management, and shadcn/ui for beautiful components. By the end, you'll have a production-ready landing page that collects email addresses for your upcoming product launch.

Prerequisites

  • Basic knowledge of React and Next.js
  • Node.js installed on your machine
  • PostgreSQL installed locally or a cloud PostgreSQL database

Project Setup

First, let's create a new Next.js project with TypeScript:

1npx create-next-app@latest waitlist-landing --typescript --tailwind --eslint
2cd waitlist-landing

Installing Dependencies

Install the required packages:

1npx shadcn@latest init -d
2npm install sequelize sequelize-cli pg pg-hstore dotenv @radix-ui/react-icons

Database Setup

1. Initialize Sequelize

Create a new directory for database configuration:

1mkdir -p db/config db/models db/migrations

Create a .sequelizerc file in your project root:

1const path = require('path');
2
3module.exports = {
4  'config': path.resolve('db/config', 'config.js'),
5  'models-path': path.resolve('db/models'),
6  'migrations-path': path.resolve('db/migrations'),
7  'seeders-path': path.resolve('db/seeders')
8};

Create db/config/config.js:

1require("dotenv").config();
2
3module.exports = {
4  development: {
5    use_env_variable: "POSTGRES_URL",
6    dialect: "postgres",
7  },
8  production: {
9    use_env_variable: "POSTGRES_URL",
10    dialect: "postgres",
11    dialectOptions: {
12      ssl: {
13        require: true,
14        rejectUnauthorized: false,
15      },
16    },
17  },
18};

2. Create Waitlist Model

Create db/models/index.ts:

1import pg from "pg";
2import { Sequelize } from "sequelize";
3
4export let sequelize: Sequelize = new Sequelize(
5  process.env.POSTGRES_URL as string,
6  {
7    dialect: "postgres",
8    ssl: false,
9    dialectModule: pg,
10    logging: false,
11  }
12);
13
14export async function closeConnection() {
15  await sequelize?.close();
16  sequelize = null as unknown as Sequelize;
17}

Create db/models/waitlist.ts:

1"use strict";
2// const { Model } = require("sequelize");
3import { DataTypes, Model } from "sequelize";
4import { sequelize } from ".";
5
6class Waitlist extends Model {}
7
8Waitlist.init(
9  {
10    id: {
11      type: DataTypes.UUID,
12      defaultValue: DataTypes.UUIDV4,
13      primaryKey: true,
14    },
15    email: {
16      type: DataTypes.STRING,
17      allowNull: false,
18      unique: true,
19      validate: {
20        isEmail: true,
21      },
22    },
23    createdAt: {
24      type: DataTypes.DATE,
25      defaultValue: DataTypes.NOW,
26    },
27  },
28  {
29    sequelize,
30    modelName: "Waitlist",
31    tableName: "waitlist",
32  }
33);
34
35export { Waitlist };

3. Create Migration

Generate a migration file:

1npx sequelize-cli migration:generate --name create-waitlist

Edit the generated migration file in db/migrations/[timestamp]-create-waitlist.js:

1'use strict';
2
3module.exports = {
4  up: async (queryInterface, Sequelize) => {
5    await queryInterface.createTable('waitlist', {
6      id: {
7        type: Sequelize.UUID,
8        defaultValue: Sequelize.UUIDV4,
9        primaryKey: true
10      },
11      email: {
12        type: Sequelize.STRING,
13        allowNull: false,
14        unique: true
15      },
16      createdAt: {
17        type: Sequelize.DATE,
18        allowNull: false
19      },
20      updatedAt: {
21        type: Sequelize.DATE,
22        allowNull: false
23      }
24    });
25  },
26
27  down: async (queryInterface) => {
28    await queryInterface.dropTable('waitlist');
29  }
30};

4. Environment Setup

Create a .env file:

1POSTGRES_URL=your_database_url

Use the Supabase database url here.

API Route for Waitlist

Create app/api/waitlist/route.ts:

1import { NextResponse } from 'next/server';
2import { Waitlist } from '@/db/models/waitlist';
3
4export async function POST(request: Request) {
5  try {
6    const { email } = await request.json();
7
8    const waitlistEntry = await Waitlist.create({ email });
9
10    return NextResponse.json({ 
11      message: "Successfully joined waitlist",
12      data: waitlistEntry 
13    }, { status: 201 });
14
15  } catch (error: any) {
16    if (error.name === 'SequelizeUniqueConstraintError') {
17      return NextResponse.json({ 
18        error: "You're already on the waitlist!" 
19      }, { status: 400 });
20    }
21
22    return NextResponse.json({ 
23      error: "Something went wrong" 
24    }, { status: 500 });
25  }
26}

Update Waitlist Form Component

First, install required shadcn components:

1npx shadcn@latest add button
2npx shadcn@latest add input
3npx shadcn@latest add toast

Update components/waitlist-form.tsx:

1'use client'
2
3import { useState } from 'react'
4import { Button } from "@/components/ui/button"
5import { Input } from "@/components/ui/input"
6import { useToast } from "@/hooks/use-toast"
7
8export function WaitlistForm() {
9  const [email, setEmail] = useState('')
10  const [loading, setLoading] = useState(false)
11  const { toast } = useToast()
12
13  const handleSubmit = async (e: React.FormEvent) => {
14    e.preventDefault()
15    setLoading(true)
16
17    try {
18      const response = await fetch('/api/waitlist', {
19        method: 'POST',
20        headers: {
21          'Content-Type': 'application/json',
22        },
23        body: JSON.stringify({ email }),
24      })
25
26      const data = await response.json()
27
28      if (!response.ok) throw new Error(data.error)
29
30      toast({
31        title: "Success!",
32        description: "You've been added to the waitlist.",
33      })
34      setEmail('')
35    } catch (error: any) {
36      toast({
37        title: "Error",
38        description: error.message,
39        variant: "destructive",
40      })
41    } finally {
42      setLoading(false)
43    }
44  }
45
46  return (
47    <form onSubmit={handleSubmit} className="flex gap-2 max-w-md mx-auto">
48      <Input
49        type="email"
50        placeholder="Enter your email"
51        value={email}
52        onChange={(e) => setEmail(e.target.value)}
53        required
54        className="bg-white/10 border-white/20"
55      />
56      <Button type="submit" disabled={loading}>
57        {loading ? "Joining..." : "Join Waitlist"}
58      </Button>
59    </form>
60  )
61}

In the layout.tsx, add the Toaster component.

1import { Toaster } from "@/components/ui/toaster";
2import { Inter } from "next/font/google";
3import "./globals.css";
4
5const inter = Inter({ subsets: ["latin"] });
6
7export const metadata = {
8  title: "LaunchBase - The Ultimate Landing Page Boilerplate",
9  description:
10    "Launch your product faster with our production-ready landing page boilerplate. Built with Next.js, Tailwind, and modern best practices.",
11};
12
13export default function RootLayout({
14  children,
15}: {
16  children: React.ReactNode;
17}) {
18  return (
19    <html lang="en">
20      <body className={inter.className}>
21        {children}
22        <Toaster />
23      </body>
24    </html>
25  );
26}

Database Initialization

Run the following commands to initialize your database:

1npx sequelize-cli db:migrate

Demo Landing Page Implementation

Let's create a beautiful landing page for our landing page boilerplate product. First, install required shadcn components:

1npx shadcn@latest add card
2npx shadcn@latest add separator

Update app/page.tsx:

1import { WaitlistForm } from '@/components/waitlist-form'
2import { Card } from '@/components/ui/card'
3import { Separator } from '@/components/ui/separator'
4import { GitHubLogoIcon, RocketIcon, LightningBoltIcon, MixIcon } from '@radix-ui/react-icons'
5
6export default function Home() {
7  return (
8    <div className="min-h-screen bg-gradient-to-b from-gray-900 via-gray-900 to-black text-white">
9      {/* Header */}
10      <header className="container mx-auto px-4 py-6 flex justify-between items-center">
11        <div className="text-xl font-bold">LaunchBase</div>
12        <a 
13          href="https://github.com/yourusername/launchbase"
14          target="_blank"
15          rel="noopener noreferrer"
16          className="flex items-center gap-2 text-white/80 hover:text-white transition-colors"
17        >
18          <GitHubLogoIcon className="w-5 h-5" />
19          <span>GitHub</span>
20        </a>
21      </header>
22
23      {/* Hero Section */}
24      <main className="container mx-auto px-4 py-20">
25        <div className="max-w-4xl mx-auto text-center">
26          <h1 className="text-6xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-600">
27            Launch Your Product Faster
28          </h1>
29          <p className="text-xl mb-12 text-gray-300">
30            A production-ready landing page boilerplate with waitlist functionality, 
31            analytics, and everything you need to launch your next big idea.
32          </p>
33
34          <div className="mb-16">
35            <WaitlistForm />
36            <p className="mt-3 text-sm text-gray-400">
37              Join 2,000+ developers building with LaunchBase
38            </p>
39          </div>
40
41          {/* Feature Cards */}
42          <div className="grid md:grid-cols-3 gap-6 mb-20">
43            <Card className="p-6 bg-white/5 border-white/10 text-left">
44              <RocketIcon className="w-10 h-10 mb-4 text-blue-400" />
45              <h3 className="text-lg font-semibold mb-2 text-white">Production Ready</h3>
46              <p className="text-gray-400">
47                Built with Next.js 14, TypeScript, and modern best practices.
48              </p>
49            </Card>
50
51            <Card className="p-6 bg-white/5 border-white/10 text-left">
52              <LightningBoltIcon className="w-10 h-10 mb-4 text-purple-400" />
53              <h3 className="text-lg font-semibold mb-2 text-white">Lightning Fast</h3>
54              <p className="text-gray-400">
55                Optimized for performance with 100/100 Lighthouse score.
56              </p>
57            </Card>
58
59            <Card className="p-6 bg-white/5 border-white/10 text-left">
60              <MixIcon className="w-10 h-10 mb-4 text-pink-400" />
61              <h3 className="text-lg font-semibold mb-2 text-white">Fully Customizable</h3>
62              <p className="text-gray-400">
63                Built with Tailwind CSS and shadcn/ui for easy customization.
64              </p>
65            </Card>
66          </div>
67        </div>
68
69        {/* Features Section */}
70        <div className="max-w-3xl mx-auto">
71          <h2 className="text-3xl font-bold text-center mb-12">Everything You Need</h2>
72
73          <div className="space-y-8">
74            {[
75              {
76                title: "Built for Scale",
77                description: "Sequelize ORM for robust database management, API routes for backend logic, and TypeScript for type safety."
78              },
79              {
80                title: "Analytics Built-in",
81                description: "Google Analytics integration out of the box. Understand your users from day one."
82              },
83              {
84                title: "SEO Optimized",
85                description: "Meta tags, OpenGraph, and Twitter cards pre-configured. Just customize and deploy."
86              },
87              {
88                title: "Modern Stack",
89                description: "Next.js 14, Tailwind CSS, shadcn/ui, TypeScript, and more. Always up to date."
90              }
91            ].map((feature, i) => (
92              <div key={i} className="flex gap-4 items-start">
93                <div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center flex-shrink-0">
94                  {i + 1}
95                </div>
96                <div>
97                  <h3 className="text-xl font-semibold mb-2">{feature.title}</h3>
98                  <p className="text-gray-400">{feature.description}</p>
99                </div>
100              </div>
101            ))}
102          </div>
103        </div>
104      </main>
105
106      {/* Footer */}
107      <footer className="container mx-auto px-4 py-12">
108        <Separator className="mb-8 bg-white/10" />
109        <div className="flex flex-col md:flex-row justify-between items-center gap-4 text-gray-400">
110          <div>Ā© 2024 LaunchBase. All rights reserved.</div>
111          <div className="flex gap-6">
112            <a href="#" className="hover:text-white transition-colors">Twitter</a>
113            <a href="#" className="hover:text-white transition-colors">GitHub</a>
114            <a href="#" className="hover:text-white transition-colors">Discord</a>
115          </div>
116        </div>
117      </footer>
118    </div>
119  )
120}

Analytics Events

Let's track important events using Google Analytics. Create lib/analytics.ts:

1type EventNames = 'join_waitlist';
2
3export const trackEvent = (eventName: EventNames, properties?: Record<string, any>) => {
4  if (typeof window !== 'undefined' && 'gtag' in window) {
5    // @ts-ignore
6    window.gtag('event', eventName, properties);
7  }
8};

Update the WaitlistForm component to track successful signups:

1// In components/waitlist-form.tsx
2import { trackEvent } from '@/lib/analytics'
3
4// Inside handleSubmit after successful submission:
5trackEvent('join_waitlist', { email });

Add Google Analytics

Install the following library

1npm i --save @next/third-parties

Update your app/layout.tsx to add GoogleAnalytics module:

1import { Toaster } from "@/components/ui/toaster";
2import { GoogleAnalytics } from "@next/third-parties/google";
3import { Inter } from "next/font/google";
4import "./globals.css";
5
6const inter = Inter({ subsets: ["latin"] });
7
8export const metadata = {
9  title: "LaunchBase - The Ultimate Landing Page Boilerplate",
10  description:
11    "Launch your product faster with our production-ready landing page boilerplate. Built with Next.js, Tailwind, and modern best practices.",
12};
13
14export default function RootLayout({
15  children,
16}: {
17  children: React.ReactNode;
18}) {
19  return (
20    <html lang="en">
21      <body className={inter.className}>
22        {children}
23        <Toaster />
24        <GoogleAnalytics gaId={(process.env.GA_ID ?? "").toString()} />
25      </body>
26    </html>
27  );
28}

Update .env to include your Google Analytics ID:

1GA_ID=G-XXXXXXXXXX

SEO Optimization

Create app/opengraph-image.png with your OG image.

Update metadata in app/layout.tsx:

1export const metadata = {
2  title: 'LaunchBase - The Ultimate Landing Page Boilerplate',
3  description: 'Launch your product faster with our production-ready landing page boilerplate. Built with Next.js, Tailwind, and modern best practices.',
4  openGraph: {
5    title: 'LaunchBase - The Ultimate Landing Page Boilerplate',
6    description: 'Launch your product faster with our production-ready landing page boilerplate.',
7    url: 'https://launchbase.yourdomain.com',
8    siteName: 'LaunchBase',
9    images: [
10      {
11        url: '/opengraph-image.png',
12        width: 1200,
13        height: 630,
14      },
15    ],
16    locale: 'en-US',
17    type: 'website',
18  },
19  twitter: {
20    card: 'summary_large_image',
21    title: 'LaunchBase - The Ultimate Landing Page Boilerplate',
22    description: 'Launch your product faster with our production-ready landing page boilerplate.',
23    creator: '@yourusername',
24    images: ['/opengraph-image.png'],
25  },
26}

Key Features of the Demo Landing Page

  1. Modern, gradient-based design
  2. Responsive layout
  3. Feature highlights with icons
  4. Social proof section
  5. Clear call-to-action
  6. Analytics integration
  7. SEO optimization
  8. Dark theme with subtle gradients
  9. Clear navigation
  10. Strong typography hierarchy

Production Deployment

  1. Push your code to GitHub
  2. For platforms like Vercel or Railway:
    • Set up your PostgreSQL database
    • Add environment variables
    • Deploy your application

Additional Production Considerations

For production deployment, you'll need to:

  1. Set up a production database (e.g., on Railway, Heroku, or AWS)
  2. Configure SSL for database connections
  3. Set up proper environment variables
  4. Run migrations during deployment

Security Enhancements

Some security considerations when using Sequelize:

  1. Input Validation:
1// In your API route
2import { z } from 'zod';
3
4const schema = z.object({
5  email: z.string().email()
6});
7
8// Validate input before processing
9const validated = schema.parse({ email });
  1. Rate Limiting:
1import rateLimit from 'express-rate-limit';
2
3const limiter = rateLimit({
4  windowMs: 15 * 60 * 1000, // 15 minutes
5  max: 5 // limit each IP to 5 requests per windowMs
6});

Conclusion

We've successfully updated our landing page to use Sequelize ORM instead of raw Supabase queries. This gives us:

  • Better type safety and validation
  • More control over database operations
  • Easier testing and migration management
  • More flexible deployment options

The combination of Next.js, Sequelize, and shadcn/ui provides a solid foundation for building production-ready applications.

Remember to:

  • Keep your Sequelize models and migrations in sync
  • Regularly backup your database
  • Monitor your database performance
  • Implement proper error handling
  • Keep your dependencies updated

If you find any challenges in setting this up, go ahead on LaunchBase site and get the full code repository and use directly.

Happy coding! šŸš€


More Stories

Cover Image for SaaS Growth Tactics: What to Expect in 2025?

SaaS Growth Tactics: What to Expect in 2025?

Discover the top SaaS growth tactics for 2025. Learn how AI, product-led growth, and Micro SaaS are reshaping the industry. Stay ahead with expert strategies!

Cover Image for Why Creators Need Voice Memo Apps Today

Why Creators Need Voice Memo Apps Today

Capture ideas anytime with voice memo apps. Learn how Voice Genie turns your recordings into social media content and streamlines creative workflows.

Cover Image for Building you own Landing Page with Waitlist using Nextjs, Supabase & ShadeCN

Building you own Landing Page with Waitlist using Nextjs, Supabase & ShadeCN

Learn how to create a professional landing page with waitlist functionality using Next.js 14, Sequelize ORM, and shadcn/ui. Complete tutorial with code examples, Google Analytics integration, and deployment guide.

Stay Updated with Our Latest Tools and Resources

Subscribe to our newsletter and be the first to know about new product launches, updates, and exclusive offers.