Zero Hassle Auth: Clerk + Next.js (With Repo)


As I moved into Next.js, I thought a different framework might solve the problem. But I soon discovered that even popular solutions like NextAuth had a steep learning curve and a lot of complex configuration, especially for a new developer.
Then, I found Clerk. It completely changed the game. Instead of wrestling with boilerplate code and complex setups, I could now add full authentication—user sign-up, sign-in, and profile management—in minutes. With just a few lines of code and some pre-built, customizable components, my application had a secure, modern authentication system. This simple, copy-and-paste approach frees you up to focus on building the features that truly matter, making the entire development process faster and far more enjoyable.
Create Your Clerk Account
Now that you're convinced of the magic, let's get our hands dirty. The beauty of Clerk is that the initial setup is incredibly fast. We'll go from a blank canvas to a fully authenticated app in just a few minutes.
First things first, you'll need a Clerk account. It's free to get started.
Head over to the Clerk website ans sign up.
Once in your dashboard, create a new application. You can give it a like "First Step"
Clerk will automatically generate your API keys. You'll see a Publishable Key and a Secret Key. These are crucial for connecting your Next.js app to Clerk, so keep them safe! We'll use them in the next step.
Optional: Creating a Next.js App
npx create-next-app@latest
After running this command, you'll be prompted with a series of questions to configure your new Next.js project, including options for TypeScript, ESLint, Tailwind CSS, and the App Router. This process quickly sets up a clean project structure, getting you ready to code in minutes.
The Clerk Integration: Just a Few Lines of Code
Step 1 - Install Clerk
npm install @clerk/nextjs
Step 2 - Set your Clerk API keys
For a Next.js project, your .env
files should be placed in the root of the project (same level as package.json
), not inside the app/
folder.
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=********
CLERK_SECRET_KEY=********
Step 3 - Update middleware.ts
For a Next.js project, your middleware.ts
files should be placed in the root of the project (same level as package.json
), not inside the app/
folder.
import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware();
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
};
Step 04 - Add ClerkProvider
in layout.tsx
file
import { type Metadata } from 'next'
import {
ClerkProvider,
SignInButton,
SignUpButton,
SignedIn,
SignedOut,
UserButton,
} from '@clerk/nextjs'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
})
const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
})
export const metadata: Metadata = {
title: 'Clerk Next.js Quickstart',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<ClerkProvider>
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<header className="flex justify-end items-center p-4 gap-4 h-16">
<SignedOut>
<SignInButton />
<SignUpButton>
<button className="bg-[#6c47ff] text-ceramic-white rounded-full font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 cursor-pointer">
Sign Up
</button>
</SignUpButton>
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</header>
{children}
</body>
</html>
</ClerkProvider>
)
}
Step 05 - Launch your Project 🎉
npm run dev
Open Clerk Dashboard
This means you don’t need to manually manage user accounts — Clerk provides a ready-made user management system where you can view:
A list of all authenticated users
Their email addresses or OAuth provider (Google, GitHub, Microsoft, etc.)
Their status (active, invited, etc.)
Securing Your App: Defining Public and Protected Paths
Update you middleware.ts
file
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isPublicRoute = createRouteMatcher([
'/', // only app/page.tsx is public and rest routes like app/dashboard.tsx in protected. Here protected means only authenticated users are allowed.
])
export default clerkMiddleware(async (auth, req) => {
if (!isPublicRoute(req)) {
await auth.protect()
}
})
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
};
This middleware protects all routes and APIs by default, except for explicitly defined public routes (like /sign-in
) and static assets.
If a user is not authenticated and tries to access a protected route like /dashboard
→
Clerk’s auth.protect()
will block them and redirect them to the sign-in page (/sign-in
) by default.
If a user is authenticated, they can access /dashboard
normally (no redirect).
Building a Full-Stack App with User-Specific Data
With our routes now securely guarded, an authenticated user can step into protected areas like their dashboard. But access is only half the story; the real magic lies in creating a personalized experience. This is where the userId
becomes the cornerstone of our application. Provided by Clerk's auth()
helper on the server, this unique identifier is the golden key we use to query our database, ensuring we fetch only the notes, projects, or settings that belong exclusively to that user.
// app/dashboard/page.tsx
import { redirect } from 'next/navigation';
import { auth } from '@clerk/nextjs';
import { prisma } from '@/lib/db';
export default async function DashboardPage() {
const { userId } = auth();
// If there's no user, redirect them to the homepage.
if (!userId) {
// 2. Replace the returned div with the redirect function
redirect('/');
}
// If the code reaches this point, you are guaranteed to have a userId.
const userTodos = await prisma.todo.findMany({
where: {
userId: userId,
},
orderBy: {
createdAt: 'desc',
},
});
return (
<div>
<h1 className="text-3xl font-bold mb-4">Your Todos</h1>
{/* ... rest of your component to display the todos ... */}
</div>
);
}
That's a Wrap!
And there you have it! We've connected all the dots: from a secure login with Clerk to fetching personalized user data. You now hold the fundamental pattern for building truly dynamic and secure web applications. Explore more
I sincerely hope you found this guide valuable and love the final result of your work. Happy coding.
Social Links
Subscribe to my newsletter
Read articles from Ashish Raut directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
