What You Build in 45 Minutes
A complete, functional SaaS application ready for user testing and validation
Simple, Focused Tech Stack
Only the essentials - no complexity, no bloat. Perfect for getting started quickly with proven technologies.
Frontend
Next.js with App Router
Server components with app router for optimal performance
TypeScript (Strict Mode)
100% type-safe development with strict TypeScript configuration
Tailwind CSS v3 (Pure)
Clean, simple Tailwind utilities with no component libraries
React Hook Form + Zod
Simple form handling with validation patterns
Auth
Supabase (Auth & Database)
Single service for authentication and PostgreSQL database
Database
Local Supabase Development
Full offline development with local Supabase service
Drizzle ORM
Type-safe database queries with migration system
Ready-to-Use Templates
Copy and customize these tested components for your project
Homepage
Complete homepage with hero section, features grid, and pricing
Authentication Pages
Sign in and sign up pages with consistent styling
Dashboard
Main dashboard with stats cards and onboarding checklist
Settings Page
Account settings with profile and billing sections
Dashboard Navigation
Simple nav bar with logo, links, and sign out button
Layouts
Marketing, auth, and dashboard layouts for consistent structure
4-Phase Setup Process
Follow the sequence for a complete minimal SaaS in under an hour
Phase 1: Foundation
Next.js project, TypeScript, environment setup, and dependencies
Phase 2: Database & Auth
Supabase setup, Drizzle schema, migrations, and authentication
Phase 3: UI
Tailwind setup, dark mode, marketing pages, and dashboard
Phase 4: Features
API routes with CRUD operations and form validation
Real Code Examples
See exactly what you get - production-ready components and patterns
Dashboard Navigation
Clean navigation component with dark mode support
import Link from 'next/link'
export function DashboardNav() {
return (
<nav className="bg-white dark:bg-gray-950 border-b border-gray-200 dark:border-gray-800">
<div className="max-w-6xl mx-auto px-4">
<div className="flex justify-between items-center h-16">
<div className="flex items-center space-x-8">
<Link href="/dashboard" className="font-bold text-xl text-gray-900 dark:text-white">
YourSaaS
</Link>
<Link
href="/dashboard"
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
>
Dashboard
</Link>
</div>
<div className="flex items-center space-x-4">
<Link
href="/dashboard/settings"
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
>
Settings
</Link>
<button className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white">
Sign Out
</button>
</div>
</div>
</div>
</nav>
)
}Drizzle Schema
Type-safe database schema with relations
import { pgTable, serial, text, timestamp, boolean } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique(),
name: text('name'),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow()
})
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false),
userId: serial('user_id').references(() => users.id),
createdAt: timestamp('created_at').defaultNow()
})
export type User = typeof users.$inferSelect
export type NewUser = typeof users.$inferInsertAPI Route with Validation
Type-safe API endpoint with Zod validation
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { db } from '@/lib/db'
import { posts } from '@/lib/db/schema'
const createPostSchema = z.object({
title: z.string().min(1).max(255),
content: z.string().optional(),
published: z.boolean().default(false)
})
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const validatedData = createPostSchema.parse(body)
const newPost = await db.insert(posts)
.values(validatedData)
.returning()
return NextResponse.json(newPost[0])
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: 'Invalid data', details: error.errors },
{ status: 400 }
)
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}Dark Mode Toggle
Simple theme switching with Tailwind classes
'use client'
import { useState, useEffect } from 'react'
import { Moon, Sun } from 'lucide-react'
export function ThemeToggle() {
const [dark, setDark] = useState(false)
useEffect(() => {
const isDark = localStorage.getItem('theme') === 'dark' ||
(!localStorage.getItem('theme') &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
setDark(isDark)
document.documentElement.classList.toggle('dark', isDark)
}, [])
const toggleTheme = () => {
const newDark = !dark
setDark(newDark)
localStorage.setItem('theme', newDark ? 'dark' : 'light')
document.documentElement.classList.toggle('dark', newDark)
}
return (
<button
onClick={toggleTheme}
className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
>
{dark ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
</button>
)
}