@clerk/nextjs (ClerkProvider in
app/layout.tsx, route protection in proxy.ts — Next 16 renames
middleware.ts → proxy.ts). The backend uses @clerk/express
(clerkMiddleware + requireAuth).
Onboarding
A signed-in user with no active org is routed byproxy.ts to
/onboarding, which renders Clerk’s <CreateOrganization>. The dashboard then
calls POST /api/orgs/sync (idempotent), which upserts the active org into the
Postgres organizations table.
This is on-demand sync — no webhooks. A Svix-verified Clerk webhook is the
production upgrade if orgs are ever created outside the app.
Teams, roles & members
Team, roles, and members are Clerk-native — there are no DB tables for them.- Roles are Clerk’s defaults:
org:adminandorg:member. - The dashboard (
/) shows a read-only People list viauseOrganization({ memberships }). /teamrenders<OrganizationProfile>for full management — member list, role changes, invites, and approving join requests. Clerk gates admin-only actions by role.- The header has
<OrganizationSwitcher>for org switching and the request-to-join button.
“Request to join” needs verified domains (enabled on the instance via
clerk enable orgs --domains). An org admin adds/verifies a domain and sets
enrollment mode to “Automatic suggestion” in /team. In dev, a domain
auto-verifies if the admin’s own email is on it.