
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
- Modern, gradient-based design
- Responsive layout
- Feature highlights with icons
- Social proof section
- Clear call-to-action
- Analytics integration
- SEO optimization
- Dark theme with subtle gradients
- Clear navigation
- Strong typography hierarchy
Production Deployment
- Push your code to GitHub
- 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:
- Set up a production database (e.g., on Railway, Heroku, or AWS)
- Configure SSL for database connections
- Set up proper environment variables
- Run migrations during deployment
Security Enhancements
Some security considerations when using Sequelize:
- 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 });
- 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! š