Supajump gives you Organizations → Teams → Users, dynamic RBAC with Row‑Level Security, RSC‑first data fetching, and a batteries‑included DX — all wired up in a Turborepo.
supabase start
+ pnpm dev
Powered by your favorite stack
Supajump packages the hard parts so you can focus on your product: auth, permissions, data fetching, and a maintainable file structure.
Organizations → Teams → Users with memberships and role assignments. Clean tenant isolation and sane defaults.
Permission = resource + action + scope (all/own). Enforced via Postgres Row Level Security and helper RPCs.
React Server Components with server‑side prefetch + client hydration using TanStack Query.
Organized by feature folders with colocated components, hooks, queries, and types.
npx/pnpm create to bootstrap a new project with env scaffolding and clear next steps.
Skeleton loaders, react‑hook‑form + zod, and sensible defaults for a production‑ready feel.
/apps/app
– Next.js application/packages/create-supajump-app
– CLI scaffold/packages/*
– shared packages@/*
, @features/*
env.mjs
Scaffold a new project with the CLI.
npx @supajump/create-app my-app
Run Supabase + the app locally.
cd my-app && supabase start && pnpm dev
Build with confidence. RBAC + RLS are already wired in.
pnpm build
apps/app/src/lib/database.types.ts
./features/[name]
./apps/app/src/queries/keys.ts
for proper invalidation.These are the real patterns the template ships with — not hand‑wavy pseudo‑code.
// RSC with server-side prefetch + hydration
import { HydrationBoundary, dehydrate } from "@tanstack/react-query"
import { getQueryClient } from "@/components/providers/get-query-client"
import { api } from "@/queries"
import { postsKeys } from "@/queries/keys"
export default async function Page({ params }) {
const supabase = await createClient()
const queryClient = getQueryClient()
await queryClient.prefetchQuery({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: postsKeys.list(orgId, teamId),
queryFn: () => api.posts.getAll(supabase, orgId, teamId),
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<PostsTable orgId={orgId} teamId={teamId} />
</HydrationBoundary>
)
}
-- RLS helper pattern (simplified)
CREATE POLICY "rls_<table>_select" ON <table>
FOR SELECT TO authenticated USING (
supajump.has_permission('<table>', 'view', org_id, team_id, owner_id)
);
CREATE POLICY "rls_<table>_insert" ON <table>
FOR INSERT TO authenticated WITH CHECK (
supajump.has_permission('<table>', 'create', org_id, team_id, owner_id)
);
CREATE POLICY "rls_<table>_update" ON <table>
FOR UPDATE TO authenticated WITH CHECK (
supajump.has_permission('<table>', 'edit', org_id, team_id, owner_id)
);
CREATE POLICY "rls_<table>_delete" ON <table>
FOR DELETE TO authenticated WITH CHECK (
supajump.has_permission('<table>', 'delete', org_id, team_id, owner_id)
);
// Client hook
export function usePosts(orgId, teamId) {
const supabase = createClient()
return useQuery({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: postsKeys.list(orgId, teamId),
queryFn: () => api.posts.getAll(supabase, orgId, teamId),
})
}
# Development
supabase start
pnpm dev
# Build / Lint
pnpm build
pnpm lint
# DB management
pnpm db:gen:types
supabase db push
supabase db reset
Supajump ships with skeleton components that mirror layouts for instant perceived performance.