DEV Community

Cover image for Next.JS Authentication with Clerk
Shashwat Pritish
Shashwat Pritish

Posted on

Next.JS Authentication with Clerk

Authentication is usually the most time-consuming part of any app… but it doesn’t have to be.

With Clerk, you can add a fully secure auth system to Next.js (App Router) in minutes — and in this guide, we’ll build a custom Sign-In / Sign-Up UI plus a protected dashboard route.


🚀 What we'll build

  • 🔐 Sign In page (custom UI)
  • 🆕 Sign Up page (custom UI)
  • 🛡 Protected route: /dashboard (only logged-in users can open it)
  • ⚡ Using Clerk hooks (useSignIn, useSignUp, useUser)

1️⃣ Install Clerk in Next.js (App Router)

npm install @clerk/nextjs
Enter fullscreen mode Exit fullscreen mode

In .env.local:

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_XXXXXXXX
CLERK_SECRET_KEY=sk_test_XXXXXXXX
Enter fullscreen mode Exit fullscreen mode

2️⃣ Wrap the app with ClerkProvider — app/layout.tsx

import { ClerkProvider } from "@clerk/nextjs";
import "./globals.css";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

3️⃣ Build custom Sign-In pageapp/sign-in/page.tsx

"use client";
import { useSignIn } from "@clerk/nextjs";
import { useState } from "react";
import { useRouter } from "next/navigation";

export default function SignInPage() {
  const { isLoaded, signIn } = useSignIn();
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  if (!isLoaded) return null;

  const handleSubmit = async (e: any) => {
    e.preventDefault();
    const res = await signIn.create({ identifier: email, password });
    if (res.status === "complete") router.push("/dashboard");
  };

  return (
    <form onSubmit={handleSubmit} className="max-w-sm mx-auto mt-20 flex flex-col gap-3">
      <h2 className="text-2xl font-semibold text-center">Sign In</h2>
      <input className="border px-3 py-2 rounded" placeholder="Email" onChange={e => setEmail(e.target.value)} />
      <input className="border px-3 py-2 rounded" placeholder="Password" type="password" onChange={e => setPassword(e.target.value)} />
      <button className="bg-black text-white py-2 rounded">Continue</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

4️⃣ Build custom Sign-Up pageapp/sign-up/page.tsx

"use client";
import { useSignUp } from "@clerk/nextjs";
import { useRouter } from "next/navigation";
import { useState } from "react";

export default function SignUpPage() {
  const { isLoaded, signUp } = useSignUp();
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  if (!isLoaded) return null;

  const handleSubmit = async (e: any) => {
    e.preventDefault();
    const res = await signUp.create({ emailAddress: email, password });
    if (res.status === "complete") router.push("/dashboard");
  };

  return (
    <form onSubmit={handleSubmit} className="max-w-sm mx-auto mt-20 flex flex-col gap-3">
      <h2 className="text-2xl font-semibold text-center">Create Account</h2>
      <input className="border px-3 py-2 rounded" placeholder="Email" onChange={e => setEmail(e.target.value)} />
      <input className="border px-3 py-2 rounded" placeholder="Password" type="password" onChange={e => setPassword(e.target.value)} />
      <button className="bg-black text-white py-2 rounded">Sign Up</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

5️⃣ Create the protected Dashboard routeapp/dashboard/page.tsx

import { auth, UserButton } from "@clerk/nextjs";

export default function DashboardPage() {
  const { userId } = auth();
  if (!userId) return <div className="text-center mt-20">Unauthorized</div>;

  return (
    <div className="max-w-xl mx-auto mt-20 text-center">
      <UserButton afterSignOutUrl="/" />
      <h1 className="text-3xl font-bold mt-6">Welcome to Dashboard 🎉</h1>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

6️⃣ Protect the route with middleware — middleware.ts

import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";

const isProtectedRoute = createRouteMatcher(["/dashboard(.*)"]);

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) auth().protect();
});

export const config = {
  matcher: ["/((?!_next|.*\\..*).*)"],
};
Enter fullscreen mode Exit fullscreen mode

🎉 Done!

You now have:

  • 🔐 Custom Sign-In page
  • 🆕 Custom Sign-Up page
  • 🛡 Protected Dashboard route
  • 🚫 Automatic access blocking via middleware

Top comments (0)