Skip to content

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/nextjs v6.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

  1. Go to https://dashboard.clerk.com/apps/app_39PolNfpHk8zsHzoCJNwwCo8XkM
  2. Click "Create Production instance" (or look for instance switcher)
  3. Clerk will generate new pk_live_ and sk_live_ keys
  4. Note the Frontend API URL — it will be something like:
  5. Default: https://clerk.morphism.systems (if you configure a custom domain)
  6. 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):

  1. User & Authentication:
  2. Email sign-up: Enabled
  3. Email verification: OTP
  4. Consider enabling: Password (optional), Google OAuth, GitHub OAuth

  5. Sessions:

  6. Set session token lifetime (default: 7 days)
  7. Enable multi-session if needed

  8. Organizations:

  9. Enable if multi-tenant is needed
  10. Configure org creation permissions

  11. Restrictions:

  12. Consider allowlist/blocklist for beta
  13. Set up invitation-only mode if not ready for public sign-up

  14. Webhooks (optional):

  15. Set up webhook endpoint for user/org events
  16. URL: https://morphism.systems/api/webhooks/clerk
  17. Secret: Store as CLERK_WEBHOOK_SECRET in 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:

  1. In morphism-systems/morphism-systems Vercel project settings:
  2. Remove morphism.systems domain
  3. In alawein/morphism Vercel project settings:
  4. Add morphism.systems domain
  5. Vercel will provide DNS records to add
  6. In DNS provider:
  7. Update A/CNAME records to point to alawein/morphism project
  8. Delete the morphism-systems Vercel team (or rename it to avoid confusion)
  9. Update .github/workflows/deploy.yml secrets 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