Next.js + Supabase: The Aussie Startup Playbook
A practical guide to building a production-ready SaaS with Next.js App Router and Supabase — from zero to deployed.
Next.js + Supabase: The Aussie Startup Playbook
This guide walks you through the real setup — the one that has survived contact with production traffic in Australian startups.
Bootstrap in 2 Minutes
# Create Next.js app
npx create-next-app@latest my-saas --typescript --app --tailwind
# Install Supabase
npm install @supabase/supabase-js
Project Structure That Scales
The file structure that works for teams of 1–15:
app/
layout.tsx # Root layout
page.tsx # Landing page
dashboard/
page.tsx # Authenticated dashboard
api/
auth/
route.ts # Auth endpoints
lib/
supabase/
client.ts # Browser client
server.ts # Server client
seed-data.ts # Static/seed data
The Two-Client Pattern
Supabase requires two clients: one for the browser, one for the server.
Browser client (lib/supabase/client.ts):
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
Server client (lib/supabase/server.ts):
import { createClient } from '@supabase/supabase-js';
export function createServerClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{ auth: { persistSession: false } }
);
}
The server client uses your service role key — never expose this to the browser.
Database Design: Keep It Boring
Most SaaS apps only need 3–5 tables to get to $10K MRR:
-- Users handled by Supabase Auth
-- Everything else is your domain
CREATE TABLE subscriptions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES auth.users NOT NULL,
plan text NOT NULL DEFAULT 'free',
created_at timestamptz DEFAULT now()
);
CREATE TABLE items (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid REFERENCES auth.users NOT NULL,
title text NOT NULL,
created_at timestamptz DEFAULT now()
);
Row Level Security (RLS) is your most important primitive:
ALTER TABLE items ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users can only see their own items"
ON items FOR ALL
USING (auth.uid() = user_id);
Deployment Checklist
Before going live on Vercel:
- [ ]
.env.localis NOT committed to git - [ ] Production env vars set in Vercel dashboard
- [ ] RLS enabled on all user-facing tables
- [ ] Rate limiting on all write API routes
- [ ] Error monitoring set up (Sentry free tier)
- [ ] Custom domain with HTTPS
Scaling in the Aussie Market
Supabase's free tier handles ~500 concurrent connections — enough for a few thousand monthly active users. When you're ready to scale:
- Upgrade to Supabase Pro ($25/month, Sydney availability coming)
- Add Redis for caching hot queries (Upstash is excellent)
- Move heavy computation to background jobs (use Trigger.dev or inngest)
The yellow brick stack will carry you well past $1M ARR.