Authentication is one of those things that sounds simple ("just add login") but has a hundred edge cases that can ruin your launch. Clerk handles the hard parts, but setting it up properly for production still requires some decisions. Here's the complete setup from zero to deployed, based on running it in production for RAXXO Studio.
Why Clerk Over Auth.js/NextAuth?
I've used both. Clerk wins for solo developers because:
- You don't manage passwords, sessions, or tokens
- The UI components are pre-built and customizable
- User management dashboard is included
- OAuth providers (Google, GitHub) are configured in a dashboard, not in code
- Webhook support for syncing user events to your database
The trade-off: Clerk is a hosted service with a pricing tier. For most projects, the free tier (10,000 monthly active users) is more than enough.
Initial Setup
Install the packages:
npm install @clerk/nextjs
Create a Clerk account, create an application, and grab your keys:
# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
Wrap your app in the ClerkProvider:
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';
export default function RootLayout({ children }) {
return (
<ClerkProvider>
<html><body>{children}</body></html>
</ClerkProvider>
);
}
Middleware: Protecting Routes
Clerk's middleware handles route protection. Create middleware.ts in your project root:
import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware();
export const config = {
matcher: ['/((?!.*\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};
Then define which routes are public and which require authentication. The pattern I use: marketing pages are public, the app is protected, API routes check auth individually.
Custom Sign-In and Sign-Up Pages
Clerk provides hosted sign-in pages, but custom pages that match your brand are better. Create them with Clerk's components:
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@clerk/nextjs';
export default function SignInPage() {
return (
<div className="your-glass-container">
<SignIn
signUpUrl="/sign-up"
appearance={{
elements: {
rootBox: 'your-custom-styles',
card: 'glass-card',
}
}}
/>
</div>
);
}
The appearance prop lets you override every CSS class. At RAXXO Studio, the sign-in page uses our glassmorphism design system, so it feels like part of the app, not a third-party login form.
Syncing Clerk Users to Your Database
Clerk manages authentication, but your app needs user data in your own database for things like plan management, usage tracking, and feature flags. The pattern:
async function getDbUser() {
const clerkUser = await currentUser();
if (!clerkUser) throw new Error('Not authenticated');
let dbUser = await db.select().from(users)
.where(eq(users.clerkId, clerkUser.id))
.limit(1);
if (!dbUser.length) {
// Auto-create on first API call
dbUser = await db.insert(users).values({
clerkId: clerkUser.id,
email: clerkUser.emailAddresses[0].emailAddress,
plan: 'free'
}).returning();
}
return dbUser[0];
}
This lazy-sync pattern means you never need a separate "onboarding" step. The first time a user hits any API route, their database record is created automatically.
Dev vs Production Instances
Clerk uses separate instances for development and production. This is important:
-
Development: Uses
pk_test_/sk_test_keys. Auth happens onclerk.accounts.dev. Perfect for local development. -
Production: Uses
pk_live_/sk_live_keys. You configure a custom domain (likeclerk.yourdomain.com). Users created in dev don't exist in production and vice versa.
Set dev keys in .env.local and production keys in your Vercel environment variables. Never mix them.
Google OAuth Setup
Adding Google sign-in through Clerk's dashboard is straightforward for development. For production, you need your own Google OAuth credentials:
- Create a project in Google Cloud Console
- Configure the OAuth consent screen (needs privacy policy URL)
- Create OAuth 2.0 credentials
- Add the client ID and secret to Clerk's production instance
The consent screen review process takes a few days. Plan for this before launch. You can use email-based sign-in while waiting for Google approval.
Webhooks for External Events
If external services need to interact with your user system (like Shopify orders triggering plan upgrades), you need to match users by email since external services don't know Clerk IDs. Build your database queries to support both Clerk ID lookup (for authenticated requests) and email lookup (for webhook processing).
Common Pitfalls
Middleware matcher: Get the regex wrong and either everything is public or everything requires auth. Test thoroughly.
Server vs client: currentUser() works in Server Components and API routes. useUser() works in Client Components. Using the wrong one gives confusing errors.
Appearance customization: The appearance prop is powerful but the documentation for specific element classes can be sparse. Use browser dev tools to find the exact class names you need to override.
RAXXO Studio uses Clerk for all authentication. Try the sign-up flow at studio.raxxo.shop.
Dieser Artikel enthält Affiliate-Links. Wenn du dich darüber anmeldest, erhalte ich eine kleine Provision - für dich entstehen keine Mehrkosten. Ich empfehle nur Tools, die ich selbst nutze. (Werbung)