Clerk Setup Guide¶
NON-NORMATIVE. Step-by-step guide for setting up Clerk authentication for morphism.systems, from current dev-only state to production-ready.
Current State (as of 2026-03-01)¶
- Clerk Application: "Morphism" (App ID:
app_3ALLeKVDWcL5BdR4yChxmpWO42G) - Development Instance:
ins_3ALMKLmoJU90cRvHyJIGpfCeTui - Production Instance: Active —
clerk.morphism.systems - Frontend API (prod):
https://clerk.morphism.systems - Frontend API (dev):
https://easy-pigeon-13.clerk.accounts.dev(old app deleted; new dev instance TBD) - SDK:
@clerk/nextjsv6.39.0 (Next.js 15 App Router) - Auth flow: Email sign-up/sign-in with OTP verification
- Users: 0 (no sign-ups yet)
- Plan: Hobby (free)
- DNS: Cloudflare (5 CNAME records verified, SSL certs provisioned)
- Vercel Production env uses
pk_live_/sk_live_keys
What Was Fixed¶
An orphaned pk_live_ key encoding clerk.morphism.systems was found on the
morphism-systems Vercel project. This caused 7 console errors per page load
because:
1. The key told Clerk SDK to load JS from clerk.morphism.systems
2. No DNS CNAME record existed for clerk.morphism.systems
3. CSP blocked the request (domain not in script-src)
Resolution: Replaced with the correct pk_test_ dev key. All errors resolved.
Architecture¶
Browser → morphism.systems (Vercel)
├── ClerkProvider (layout.tsx) — wraps entire app
├── clerkMiddleware (middleware.ts) — protects routes, adds auth to requests
├── Publishable key: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY (client-side)
└── Secret key: CLERK_SECRET_KEY (server-side, middleware + API routes)
Clerk SDK → easy-pigeon-13.clerk.accounts.dev (Clerk's servers)
├── Loads clerk-js bundle (script tag injected by ClerkProvider)
├── Handles sign-in/sign-up UI (Clerk components)
└── Session management (JWT tokens)
Key Files¶
| File | Purpose |
|---|---|
apps/morphism/src/app/layout.tsx |
<ClerkProvider> wraps app shell |
apps/morphism/src/middleware.ts |
clerkMiddleware() with rate limiting, CSRF |
apps/morphism/next.config.ts |
CSP headers allowing Clerk domains |
apps/morphism/.env.local.example |
Template for local development |
CSP Configuration (next.config.ts)¶
The Content-Security-Policy header must allow Clerk's domains:
script-src: https://*.clerk.accounts.dev https://clerk.com https://*.clerk.com
connect-src: https://*.clerk.accounts.dev https://clerk.com https://*.clerk.com
frame-src: https://*.clerk.accounts.dev https://*.clerk.com
img-src: https://*.clerk.com https://img.clerk.com
For a production instance with a custom domain (e.g., clerk.morphism.systems),
add that domain to script-src and connect-src.
Step-by-Step: Create Production Instance¶
Prerequisites¶
- Access to Clerk Dashboard: https://dashboard.clerk.com
- Access to Vercel Dashboard: https://vercel.com/alawein/morphism
- Access to DNS provider (Google Domains for morphism.systems)
Step 1: Create Production Instance in Clerk¶
- Go to https://dashboard.clerk.com/apps/app_39PolNfpHk8zsHzoCJNwwCo8XkM
- Click "Create Production instance" (or look for instance switcher)
- Clerk will generate new
pk_live_andsk_live_keys - Note the Frontend API URL — it will be something like:
- Default:
https://clerk.morphism.systems(if you configure a custom domain) - OR:
https://<slug>.clerk.com(if you use Clerk's default domain)
Step 2: Choose Domain Strategy¶
Option A — Clerk's Default Domain (recommended for simplicity):
- No DNS changes needed
- Frontend API will be https://<slug>.clerk.com
- CSP already allows *.clerk.com — no code changes needed
- Just update env vars in Vercel
Option B — Custom Domain (clerk.morphism.systems):
- Requires DNS CNAME record
- Add in DNS: clerk CNAME frontend-api.clerk.services
- In Clerk Dashboard → Domains → Add clerk.morphism.systems
- Add clerk.morphism.systems to CSP script-src and connect-src
- More professional but more setup
Step 3: Update Vercel Environment Variables¶
For the alawein/morphism Vercel project:
# Remove old dev keys from Production environment
vercel env rm NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY production --yes
vercel env rm CLERK_SECRET_KEY production --yes
# Add new production keys
echo "pk_live_YOUR_NEW_KEY" | vercel env add NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY production
echo "sk_live_YOUR_NEW_KEY" | vercel env add CLERK_SECRET_KEY production
For the morphism-systems/morphism-systems Vercel project (if still in use):
- Same process, but link to that project first
- Or use the Vercel Dashboard UI
Keep dev keys for Development and Preview environments.
Step 4: Update CSP (if using custom domain)¶
Only needed for Option B above. In apps/morphism/next.config.ts:
// Add clerk.morphism.systems to script-src and connect-src
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com https://*.clerk.accounts.dev https://clerk.com https://*.clerk.com https://clerk.morphism.systems https://unpkg.com",
"connect-src 'self' https://*.supabase.co wss://*.supabase.co https://*.clerk.accounts.dev https://clerk.com https://*.clerk.com https://clerk.morphism.systems https://api.stripe.com https://*.sentry.io",
Step 5: Configure Production Auth Settings¶
In Clerk Dashboard (Production instance):
- User & Authentication:
- Email sign-up: Enabled
- Email verification: OTP
-
Consider enabling: Password (optional), Google OAuth, GitHub OAuth
-
Sessions:
- Set session token lifetime (default: 7 days)
-
Enable multi-session if needed
-
Organizations:
- Enable if multi-tenant is needed
-
Configure org creation permissions
-
Restrictions:
- Consider allowlist/blocklist for beta
-
Set up invitation-only mode if not ready for public sign-up
-
Webhooks (optional):
- Set up webhook endpoint for user/org events
- URL:
https://morphism.systems/api/webhooks/clerk - Secret: Store as
CLERK_WEBHOOK_SECRETin Vercel
Step 6: Redeploy and Verify¶
# Trigger a new deployment to pick up env var changes
vercel deploy --prod
# Verify the production site loads without Clerk errors
curl -s https://morphism.systems | grep -o 'publishableKey[^,}]*'
# Should show pk_live_...
# Check for console errors (use browser DevTools)
# Should be 0 Clerk-related errors
Vercel Project Consolidation¶
The current dual-project setup (alawein/morphism + morphism-systems/morphism-systems)
needs consolidation. Steps:
- In
morphism-systems/morphism-systemsVercel project settings: - Remove
morphism.systemsdomain - In
alawein/morphismVercel project settings: - Add
morphism.systemsdomain - Vercel will provide DNS records to add
- In DNS provider:
- Update A/CNAME records to point to
alawein/morphismproject - Delete the
morphism-systemsVercel team (or rename it to avoid confusion) - Update
.github/workflows/deploy.ymlsecrets if needed
Troubleshooting¶
"clerk.morphism.systems" errors in console¶
Cause: The Clerk publishable key encodes the Frontend API domain. If the key encodes a domain that doesn't resolve (no DNS record), every page load fails.
Fix: Decode the key to check: echo "<base64-part>" | base64 -d
The part after pk_test_ or pk_live_ is base64-encoded. It should decode to
a valid Clerk domain (e.g., easy-pigeon-13.clerk.accounts.dev or <slug>.clerk.com).
CSP blocks Clerk requests¶
Check that the Clerk domain appears in:
- script-src (for loading clerk-js bundle)
- connect-src (for API calls)
- frame-src (for OAuth popups)
- img-src (for user avatars)
Middleware errors¶
The middleware.ts uses clerkMiddleware() from @clerk/nextjs/server.
It runs on every request and adds auth context. If Clerk keys are invalid,
middleware will fail silently (returns signed-out state).
Environment Variable Reference¶
| Variable | Where | Purpose |
|---|---|---|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY |
Client + Server | Identifies Clerk app, encodes Frontend API URL |
CLERK_SECRET_KEY |
Server only | Backend API authentication |
NEXT_PUBLIC_CLERK_SIGN_IN_URL |
Client | Sign-in page path (default: /sign-in) |
NEXT_PUBLIC_CLERK_SIGN_UP_URL |
Client | Sign-up page path (default: /sign-up) |
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL |
Client | Redirect after sign-in (default: /dashboard) |
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL |
Client | Redirect after sign-up (default: /dashboard) |
CLERK_WEBHOOK_SECRET |
Server only | Webhook signature verification |