How to Build a Full SaaS Product With Next.js, Supabase, and Groq — For Free
Build18 min read·April 20, 2026·--

How to Build a Full SaaS Product With Next.js, Supabase, and Groq — For Free

The complete, step-by-step guide to shipping a real SaaS from zero to deployed without paying a dollar. Stack, database, auth, AI, payments — all covered.

@
@kivorablog
April 20, 2026
Share

What You'll Build


By the end of this guide you will have a deployed, production-ready SaaS application with:


  • User authentication (email/password + magic link)
  • A protected dashboard
  • AI-powered features using Groq
  • A database with row-level security
  • Rate limiting
  • A working payment flow (optional)

This is not a toy. This is a real architecture used by companies generating thousands of dollars per month.


The Stack

Free Tier (Takes You to $10k MRR)

ToolWhat It DoesFree LimitCost After
Next.js 14Full-stack frameworkFree foreverFree forever
Cloudflare PagesHosting + CDNUnlimited bandwidthFree forever
SupabaseDatabase + Auth + Storage500MB DB, 50k users$25/month
GroqAI inference14,400 req/day~$0.27/million tokens
GitHub ActionsCron + CI/CD2,000 min/month$4/month
ResendTransactional email3,000 emails/month$20/month
Upstash RedisRate limiting + cache10,000 req/day$10/month

Total free tier cost: $0/month

Paid Tier (When You're Scaling Past $10k MRR)

ToolReplacesCostWhy Upgrade
PlanetScaleSupabase DB$39/monthBetter performance at scale
VercelCloudflare Pages$20/monthBetter Next.js support, analytics
OpenAI GPT-4oGroqPay-per-tokenMore capable for complex AI tasks
PostmarkResend$15/monthBetter deliverability
RailwayGitHub Actions jobs$5/monthAlways-on background workers

Step 1: Scaffold the Project

Open your terminal and run:

npx create-next-app@latest my-saas \
  --javascript \
  --tailwind \
  --app \
  --no-src-dir \
  --no-import-alias

cd my-saas
npm install @supabase/supabase-js @supabase/auth-helpers-nextjs groq-sdk

Your folder structure will look like this:

my-saas/
├── app/
│   ├── layout.jsx
│   ├── page.jsx
│   └── api/
├── components/
├── lib/
└── public/

Step 2: Set Up Supabase

Create the Project

  • Go to supabase.com and create a free account
  • Click New Project
  • Choose a name, database password, and region closest to your users
  • Wait 2 minutes for the project to spin up

Get Your Keys

Go to Settings → API and copy:

  • Project URL
  • anon public key
  • service_role secret key (never expose this in the browser)

Create Your .env.local File

NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_KEY=your-service-role-key
GROQ_API_KEY=your-groq-key
NEXT_PUBLIC_SITE_URL=http://localhost:3000

Create the Database Schema

Go to SQL Editor in Supabase and run:

-- User profiles (extends auth.users)
create table profiles (
  id uuid primary key references auth.users(id) on delete cascade,
  email text,
  plan text default 'free',
  created_at timestamptz default now()
);

-- Auto-create profile when user signs up
create or replace function handle_new_user()
returns trigger as $$
begin
  insert into profiles (id, email)
  values (new.id, new.email);
  return new;
end;
$$ language plpgsql security definer;

create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure handle_new_user();

-- Enable RLS
alter table profiles enable row level security;

-- Users can only see and edit their own profile
create policy "users own profile"
  on profiles for all
  using (auth.uid() = id);

Step 3: Wire Up Authentication

Create the Supabase Client

// lib/supabase.js
import { createClient } from '@supabase/supabase-js'

// Server-side — full database access
export const supabaseAdmin = createClient(
  process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.SUPABASE_SERVICE_KEY
)

// Client-side — limited by RLS
export const supabasePublic = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)

Create the Auth Page

// app/auth/page.jsx
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { supabasePublic } from '@/lib/supabase'

export default function AuthPage() {
  const router = useRouter()
  const [email, setEmail]       = useState('')
  const [password, setPassword] = useState('')
  const [mode, setMode]         = useState('signin')
  const [error, setError]       = useState('')

  async function submit() {
    setError('')
    const { error } =
      mode === 'signup'
        ? await supabasePublic.auth.signUp({ email, password })
        : await supabasePublic.auth.signInWithPassword({ email, password })

    if (error) { setError(error.message); return }
    router.push('/dashboard')
  }

  return (
    <div className="min-h-screen flex items-center justify-center">
      <div className="w-full max-w-sm space-y-4">
        <h1 className="text-2xl font-bold">
          {mode === 'signin' ? 'Sign In' : 'Create Account'}
        </h1>
        <input
          type="email"
          placeholder="Email"
          value={email}
          onChange={e => setEmail(e.target.value)}
          className="w-full border rounded-lg px-4 py-2"
        />
        <input
          type="password"
          placeholder="Password"
          value={password}
          onChange={e => setPassword(e.target.value)}
          className="w-full border rounded-lg px-4 py-2"
        />
        {error && <p className="text-red-500 text-sm">{error}</p>}
        <button onClick={submit} className="w-full bg-black text-white py-2 rounded-lg">
          {mode === 'signin' ? 'Sign In' : 'Sign Up'}
        </button>
        <button onClick={() => setMode(m => m === 'signin' ? 'signup' : 'signin')}
          className="text-sm text-gray-500 w-full text-center">
          {mode === 'signin' ? 'Need an account?' : 'Already have one?'}
        </button>
      </div>
    </div>
  )
},

Protect Routes With Middleware

// middleware.js
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'

export async function middleware(req) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })
  const { data: { session } } = await supabase.auth.getSession()

  if (!session && req.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/auth', req.url))
  }
  return res
},

export const config = { matcher: ['/dashboard/:path*'] }

Step 4: Add AI Features With Groq

Get your free API key at console.groq.com. The free tier gives you 14,400 requests per day — enough to power a real product.

// app/api/generate/route.js
import Groq from 'groq-sdk'
import { supabaseAdmin } from '@/lib/supabase'

const groq = new Groq({ apiKey: process.env.GROQ_API_KEY })

export async function POST(req) {
  // Verify the user is logged in
  const authHeader = req.headers.get('authorization')
  const token = authHeader?.split(' ')[1]

  if (!token) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }

  const { data: { user } } = await supabaseAdmin.auth.getUser(token)
  if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 })

  const { prompt } = await req.json()

  const completion = await groq.chat.completions.create({
    model: 'llama-3.3-70b-versatile',
    messages: [
      {
        role: 'system',
        content: 'You are a helpful AI assistant.'
      },
      { role: 'user', content: prompt }
    ]
  })

  return Response.json({
    result: completion.choices[0].message.content
  })
},

Step 5: Deploy to Cloudflare Pages

Connect to GitHub

  • Push your code to a GitHub repository
  • Go to pages.cloudflare.com
  • Click Create a ProjectConnect to Git
  • Select your repository

Configure Build Settings

SettingValue
Framework presetNext.js
Build command`npx next build`
Output directory`.next`
Node.js version`20`

Add Environment Variables

In Settings → Environment Variables, add all variables from your .env.local file. Make sure to add both the NEXT_PUBLIC_ variables AND the server-side secrets.

Update Your Site URL

Once deployed, update NEXT_PUBLIC_SITE_URL from http://localhost:3000 to your actual Cloudflare Pages URL.


Step 6: Add Rate Limiting

Without rate limiting, a single bad actor can exhaust your Groq free tier in minutes. Add this to every API route:

// lib/ratelimit.js
const requests = new Map()

export function rateLimit(identifier, maxPerMinute = 10) {
  const now     = Date.now()
  const window  = now - 60_000
  const history = (requests.get(identifier) || [])
    .filter(ts => ts > window)

  if (history.length >= maxPerMinute) {
    return { success: false, remaining: 0 }
  }

  requests.set(identifier, [...history, now])
  return { success: true, remaining: maxPerMinute - history.length - 1 }
},

Use it in any route:

const ip     = req.headers.get('x-forwarded-for') || 'unknown'
const result = rateLimit(ip)

if (!result.success) {
  return Response.json(
    { error: 'Too many requests. Slow down.' },
    { status: 429 }
  )
},

Common Mistakes to Avoid

MistakeWhat Goes WrongFix
Exposing `SUPABASE_SERVICE_KEY` in the browserAnyone can bypass RLS and read all user dataOnly use service key in server-side API routes
No rate limitingFree API limits exhausted by botsAdd rate limiting to every AI endpoint
Skipping RLSAny user can read other users' dataEnable RLS on every table from day one
No error boundariesOne crashed component breaks the whole pageWrap dynamic sections in ``
Large bundle sizesSlow loading on mobile networksUse dynamic imports for heavy components

What to Build Next

Once the foundation is working, these are the next features that turn a demo into a business:

  • Onboarding flow — ask users their goal in 3 questions, personalise their experience
  • Usage tracking — track which features users actually use
  • Email drip sequence — automated 7-email onboarding sequence via Resend
  • Stripe or Paystack integration — monetise your active users
  • Admin dashboard — see all users, usage, revenue in one place

This stack — Next.js + Supabase + Groq + Cloudflare — is how Kivora itself is built. It costs $0 per month until you're making real money, and it scales comfortably past $10,000 MRR before you need to think about upgrading anything.

Read more on Kivora Blog

Read more on Kivora Blog

Get started →