Skip to content
🚀 From Zero to Production

Build Your FirstAI-Powered SaaS

A complete, step-by-step guide to building production-ready AI applications. Learn the exact patterns, tools, and strategies used to ship real products that scale.

Production-Tested
Complete Tutorial
Real-World Examples

What you'll learn: Monorepo setup, AI integration, authentication, payments, deployment, and everything in between.

Getting Started: Build Your SaaS Foundation

Ready to build your AI-powered SaaS? Let's walk through setting up everything from scratch. This is the exact foundation used in production applications.

Step 1: Set Up Your Monorepo

Start with a pnpm workspace monorepo. This gives you shared packages, consistent tooling, and simplified dependency management from day one.

Created terminal
+12
+# Initialize your monorepo
+pnpm init
+echo "packages:
+ - 'apps/*'
+ - 'packages/*'" > pnpm-workspace.yaml
+
+# Create directory structure
+mkdir -p apps/main packages/schema packages/components
+
+# Install dependencies
+pnpm add -D next@latest react@latest typescript@latest
+pnpm add @supabase/supabase-js ai @openrouter/ai-sdk-provider

💡 Why monorepo? Shared types, reusable components, and one command to build everything. It scales with your team.

Step 2: Configure Supabase for Backend

Supabase gives you PostgreSQL, authentication, and real-time features in one package. Set up your project and configure environment variables.

Created .env.local
+4
+# .env.local
+NEXT_PUBLIC_SUPABASE_URL=your-project-url
+NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
+SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

Create Tables

Use Supabase SQL editor to create your data models. Enable Row-Level Security (RLS) on all tables.

Enable Auth

Enable email/password authentication in Supabase dashboard. Add OAuth providers if needed.

Step 3: Integrate AI with OpenRouter

Add AI capabilities using the Vercel AI SDK and OpenRouter. This gives you access to multiple AI models through a unified API.

Created terminal
+5
+# Install AI dependencies
+pnpm add ai @ai-sdk/react @openrouter/ai-sdk-provider
+
+# .env.local
+OPENROUTER_API_KEY=your-openrouter-key

💡 Why OpenRouter? Access to 200+ AI models through one API. Switch models without code changes. Cost optimization built-in.

Step 4: Set Up Stripe for Payments

Add payment processing with Stripe. Create products, set up pricing, and configure webhooks for subscription management.

Created terminal
+7
+# Install Stripe
+pnpm add stripe
+
+# .env.local
+STRIPE_SECRET_KEY=sk_test_...
+STRIPE_WEBHOOK_SECRET=whsec_...
+STRIPE_PRICE_ID=price_...

⚠️ Important: Always verify webhook signatures before processing events. Never skip this, even in development.

Step 5: Start Development

With everything configured, start your development server and begin building your features.

Created terminal
+8
+# Start development
+pnpm dev
+
+# Build for production
+pnpm build
+
+# Start production server
+pnpm start

Next steps: Build your core features, implement authentication flows, add payment processing, and iterate based on user feedback.

Building AI-Powered Features

Now that your foundation is set, let's dive into integrating AI capabilities. I'll show you how to add streaming responses, control costs, and structure your prompts for production use.

01. Streaming with Vercel AI SDK

The Vercel AI SDK makes streaming AI responses incredibly simple. It handles Server-Sent Events (SSE), automatic reconnection, and error handling so you don't have to.

Created ChatComponent.tsx
+23
+import { useChat } from '@ai-sdk/react';
+
+function ChatComponent() {
+ const { messages, handleSubmit, isLoading } = useChat({
+ api: '/api/chat',
+ body: { conversationId },
+ headers: { Authorization: `Bearer ${token}` },
+ });
+
+ return (
+ <div>
+ {messages.map(m => (
+ <div key={m.id}>{m.content}</div>
+ ))}
+ <form onSubmit={handleSubmit}>
+ <input name="content" placeholder="Type a message..." />
+ <button type="submit" disabled={isLoading}>
+ {isLoading ? 'Sending...' : 'Send'}
+ </button>
+ </form>
+ </div>
+ );
+}

💡 Pro tip: The SDK automatically handles network reconnection. If a user's connection drops mid-stream, it will resume where it left off.

02. Rate Limiting & Cost Control

AI costs can spiral quickly. Implement two-tier rate limiting: IP-based limits prevent abuse, while per-user limits control individual usage patterns.

IP-Based Limits

Prevent spam from single sources using sliding window algorithms with Redis-backed storage.

User-Based Limits

Control costs per user account. Track usage across sessions and implement tiered limits based on subscription status.

⚠️ Critical: Always implement rate limiting before going to production. A single user can easily rack up hundreds of dollars in AI costs without limits.

03. Prompt Engineering Best Practices

The quality of your AI responses depends entirely on your prompts. Here's the structure I've found works best in production:

1

Role Definition

"You are an expert portfolio reviewer with 10 years of experience in tech hiring..."

2

Context Injection

Provide relevant data, user history, and specific constraints for the task.

3

Response Guidelines

Specify format (JSON, markdown), tone (professional, casual), and output structure.

💡 Pro tip: Test your prompts with edge cases. What happens if the user provides no data? What if the data is malformed? Handle these gracefully.

04. Structured Widget Rendering

Want your AI to trigger UI components? Use command delimiters. This pattern lets the AI request specific data displays while maintaining conversational flow.

Created widget-handler.ts
+11
+// In your prompt, tell the AI:
+"When you want to show structured data, use these delimiters:
+<<<WIDGET:projects>>>
+<<<WIDGET:skills:0,1,2>>>
+<<<MSG_BREAK>>>
+
+// In your streaming handler, parse and emit events:
+if (chunk.includes('<<<WIDGET:')) {
+ const widgetType = chunk.match(/<<<WIDGET:(\w+)>>>/)[1];
+ emit('widget', { type: widgetType });
+}

💡 Use case: Perfect for portfolio sites where AI can trigger project cards, skill visualizations, or timeline widgets mid-conversation.

Architecture Patterns That Scale

Building a SaaS that lasts requires solid architecture. Let me share the patterns I've used in production that scale with your team and your users.

01. Monorepo with pnpm Workspaces

Monorepos aren't just for big companies. They give you shared packages, consistent tooling, and simplified dependency management from day one. I use pnpm workspaces for efficient linking and fast installs.

Created pnpm-workspace.yaml
+12
+# pnpm-workspace.yaml
+packages:
+ - 'apps/*'
+ - 'packages/*'
+
+# apps/main/package.json
+{
+ "dependencies": {
+ "@yourapp/schema": "workspace:*",
+ "@yourapp/components": "workspace:*"
+ }
+}

Benefits

  • • Shared type definitions
  • • Reusable UI components
  • • Consistent tooling
  • • Single source of truth

Best Practices

  • • Use workspace protocol
  • • Semantic versioning
  • • Clear package boundaries
  • • Turbopack for builds

💡 Pro tip: Start with a monorepo even for small projects. The overhead is minimal, and you'll thank yourself when you need to add a second app or shared package.

02. Component Design Systems

Don't rebuild the wheel. Build a component library with Radix UI primitives, Tailwind CSS, and Framer Motion. This creates consistent UI patterns across your application and speeds up development.

Primitives First

Start with Radix UI for accessible, unstyled components. Add styling with Tailwind CSS for consistency. This combo gives you accessibility out of the box.

Compound Components

Use compound component patterns for complex UI elements (dialogs, dropdowns, menus). This makes your components more composable and easier to use.

Animation System

Framer Motion for smooth animations. Keep animations subtle and purposeful - never gratuitous. Animations should guide attention, not distract.

💡 Pro tip: Extract common patterns early. If you find yourself copying and pasting component code, it's time to create a reusable component in your design system.

03. API Design Patterns

Good API design makes your frontend code cleaner and your backend more maintainable. Design RESTful APIs with clear resource boundaries and proper HTTP semantics.

Created api-routes.ts
+12
+// Good API design - clear and predictable
+GET /api/portfolio/:username
+POST /api/portfolio/:username/chat
+PUT /api/settings
+DELETE /api/subscription
+
+// Response format - consistent envelope
+{
+ "data": { ... },
+ "error": null,
+ "meta": { "timestamp": "2024-01-15T10:30:00Z" }
+}

💡 Principles: Use nouns for resources, HTTP verbs for actions, plural nouns for collections, and consistent response envelopes with error handling.

Authentication & Security

Security isn't optional. Let me show you how to implement robust authentication with Supabase, row-level security, and webhook verification patterns that keep your users safe.

01. Supabase Row-Level Security

Row-Level Security (RLS) is your first line of defense. It enforces data access rules at the database level, so even if someone bypasses your application logic, they can't access data they shouldn't.

Created rls-policy.sql
+8
+-- RLS policy example
+CREATE POLICY "Users can view own data"
+ON profiles FOR SELECT
+USING (auth.uid() = user_id);
+
+CREATE POLICY "Users can update own data"
+ON profiles FOR UPDATE
+USING (auth.uid() = user_id);

Why RLS Matters

Database-level security means even API bugs can't leak data. It's defense in depth.

Best Practices

Enable RLS on all tables. Never rely solely on application-level checks.

⚠️ Critical: Never trust client-side data validation. Always enforce rules at the database level with RLS policies.

02. Webhook Signature Verification

Webhooks are powerful but dangerous if not verified. Always verify signatures before processing events to prevent attackers from sending fake events to your endpoints.

Created webhook-verify.ts
+9
+import Stripe from 'stripe';
+const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
+
+// Verify webhook signature
+const event = stripe.webhooks.constructEvent(
+ payload,
+ signature,
+ webhookSecret
+);

⚠️ Critical: Never skip signature verification, even in development. Use different webhook secrets for different environments.

03. Security Headers

Set comprehensive security headers to protect against common vulnerabilities. Configure these in Next.js middleware or next.config.

HSTS

Force HTTPS connections with max-age and includeSubDomains to prevent protocol downgrade attacks.

CSP

Content Security Policy to prevent XSS attacks with strict allowlists for scripts, styles, and other resources.

X-Frame-Options

Prevent clickjacking attacks with SAMEORIGIN or DENY to control frame embedding.

Referrer-Policy

Control referrer information leakage with strict-origin-when-cross-origin for privacy.

Payment Integration

Monetizing your SaaS with Stripe doesn't have to be complicated. Let me show you how to handle subscriptions, webhooks, and billing with proper API version handling.

01. Webhook Signature Verification

Always verify webhook signatures before processing events. This is your primary defense against fraudulent payment events.

Created webhook-handler.ts
+13
+// Verify before processing
+try {
+ const event = stripe.webhooks.constructEvent(
+ payload,
+ signature,
+ webhookSecret
+ );
+} catch (err) {
+ return NextResponse.json(
+ { error: "Invalid signature" },
+ { status: 400 }
+ );
+}

⚠️ Critical: Never skip verification - even in development. Use different webhook secrets for different environments.

02. Idempotency Checks

Stripe may retry webhook events. Implement idempotency to prevent duplicate processing of the same event.

Created idempotency.ts
+10
+// Check if already processed
+const { data: existing } = await supabase
+ .from("processed_webhooks")
+ .select("event_id")
+ .eq("event_id", eventId)
+ .maybeSingle();
+
+if (existing) {
+ return NextResponse.json({ received: true });
+}

💡 Why it matters: Without idempotency, you could charge users multiple times or create duplicate database records.

03. API Version Handling

Stripe API versions have breaking changes. The Dahlia API version moved subscription fields to items.data[]. Handle this gracefully.

Created api-version.ts
+8
+// Handle API version differences
+let currentPeriodStart = subscription.current_period_start;
+if (typeof currentPeriodStart !== 'number') {
+ const items = subscription.items?.data;
+ if (items?.[0]?.current_period_start) {
+ currentPeriodStart = items[0].current_period_start;
+ }
+}

💡 Best practice: Always check for field existence and provide fallbacks. Document API version requirements in your code.

Deployment Strategy

Shipping your SaaS to production requires more than just pushing code. Let me share the deployment patterns I use for reliable, automated releases.

01. NPM Package Publishing

If you're building reusable packages, publish them to npm with semantic versioning and trusted publishing. Use Changesets for automated version management.

Created changeset-workflow.sh
+5
+# Changeset workflow
+pnpm changeset # Choose packages and bump type
+pnpm changeset version # Update versions
+git commit -am "chore: versions"
+git push # CI publishes to npm

Trusted Publishing

Use npm OIDC authentication for secure CI/CD without managing tokens.

Semantic Versioning

Follow semver strictly: major for breaking changes, minor for features, patch for fixes.

💡 Pro tip: Configure trusted publishing in npm package settings. No more managing tokens or secrets in CI.

02. CI/CD Pipeline

Automate your deployment with GitHub Actions. Run tests, build your application, and deploy to production automatically on merge to main.

Created deploy.yml
+17
+# .github/workflows/deploy.yml
+name: Deploy
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ - run: pnpm install
+ - run: pnpm build
+ - run: pnpm test
+ # Add your deployment step here

💡 Best practice: Run tests and linting in CI before deployment. Catch issues before they reach production.

03. Environment Management

Manage environment variables securely across development, staging, and production. Never commit secrets to git.

Created .env.example
+9
+# .env.example (commit this)
+DATABASE_URL=
+NEXT_PUBLIC_API_URL=
+STRIPE_SECRET_KEY=
+
+# .env.local (never commit)
+DATABASE_URL=postgresql://...
+NEXT_PUBLIC_API_URL=https://api.example.com
+STRIPE_SECRET_KEY=sk_test_...

Development

Use .env.local for local development. Add to .gitignore.

Production

Set environment variables in your hosting platform (Vercel, Railway, etc.).

Ready to Start Building?

You now have everything you need to build a production-ready AI SaaS. Start with the Getting Started section above and work your way through each topic.

🎯 Your First Steps

  • • Set up your monorepo with pnpm
  • • Configure Supabase for backend
  • • Integrate AI with OpenRouter
  • • Add Stripe for payments

🚀 Going to Production

  • • Implement RLS policies
  • • Set up CI/CD pipeline
  • • Configure environment variables
  • • Monitor and iterate

Remember: Start small, iterate fast, and always prioritize security. These patterns have been tested in production and will scale with your application.

Built with real-world production experience. Patterns tested at scale.