Emerald City·frontend·12 min read

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.local is 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:

  1. Upgrade to Supabase Pro ($25/month, Sydney availability coming)
  2. Add Redis for caching hot queries (Upstash is excellent)
  3. Move heavy computation to background jobs (use Trigger.dev or inngest)

The yellow brick stack will carry you well past $1M ARR.