| name | ref-supabase |
| description | Reference for Supabase JavaScript client with Next.js App Router. Covers client setup for server/client components, database operations, and realtime subscriptions. Consult when working with the database layer, writing queries, or debugging Supabase connection issues. |
Supabase + Next.js Reference
Packages
bun add @supabase/supabase-js @supabase/ssr
@supabase/supabase-js — Core client
@supabase/ssr — Server-side rendering helpers for Next.js App Router
Environment Variables
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key # server-only, never expose
Client Setup
Server Components / Route Handlers (cookie-based)
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
export async function createClient() {
const cookieStore = await cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() { return cookieStore.getAll(); },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
},
},
}
);
}
Client Components (browser)
import { createBrowserClient } from "@supabase/ssr";
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
}
Admin Client (service role, server-only)
import { createClient } from "@supabase/supabase-js";
export const supabaseAdmin = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
Common Operations
Insert
const { data, error } = await supabase
.from("problems")
.insert({ title, statement_md, language, difficulty })
.select()
.single();
Select
const { data, error } = await supabase
.from("problems")
.select("*")
.eq("user_id", userId)
.order("created_at", { ascending: false });
Update
const { data, error } = await supabase
.from("skill_profiles")
.update({ summary_text: newSummary, domain_scores_json: scores })
.eq("user_id", userId)
.select()
.single();
Upsert
const { data, error } = await supabase
.from("skill_profiles")
.upsert({ user_id: userId, summary_text: summary })
.select()
.single();
Database Schema (for CodeGym)
create table problems (
id uuid primary key default gen_random_uuid(),
title text not null,
statement_md text not null,
language text not null,
difficulty text not null,
tags text[] default '{}',
skeleton text not null,
test_harness text not null,
solution_code text not null,
prompt_used text,
user_id uuid references auth.users(id),
created_at timestamptz default now()
);
create table submissions (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id),
problem_id uuid references problems(id),
code text not null,
passed int not null default 0,
total int not null default 0,
stdout text,
stderr text,
created_at timestamptz default now()
);
create table mcq_sessions (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id),
topic text not null,
difficulty_level int not null default 5,
score int not null default 0,
questions_json jsonb not null,
answers_json jsonb,
created_at timestamptz default now()
);
skill_profiles (
user_id uuid auth.users(id),
summary_text text,
strengths text[] ,
weaknesses text[] ,
domain_scores_json jsonb ,
updated_at timestamptz now()
);
RLS Policies (enable Row Level Security)
alter table problems enable row level security;
create policy "Users can manage own problems" on problems
for all using (auth.uid() = user_id);
alter table submissions enable row level security;
create policy "Users can manage own submissions" on submissions
for all using (auth.uid() = user_id);
alter table skill_profiles enable row level security;
create policy "Users can manage own profile" on skill_profiles
for all using (auth.uid() = user_id);
Gotchas
- Two clients needed: Server Components use
@supabase/ssr with cookies, Client Components use createBrowserClient
NEXT_PUBLIC_ prefix required for env vars used client-side
- Service role key bypasses RLS — only use server-side for admin operations
select() after insert/update — needed to get the returned data
.single() — use when expecting exactly one row, throws if 0 or 2+
- Arrays in Postgres — use
text[] type, Supabase JS handles them as JS arrays
- JSONB columns — stored/retrieved as plain JS objects automatically