tutorial
14 min read
4/27/2024
User Authentication in Next.js 14: A Simple Step-by-Step Tutorial
Learn how Clerk simplifies user authentication! Follow this step-by-step tutorial on how to implement secure login features on both the client and server sides of your Next.js 14 app in under 5 minutes! Style components and pages using the popular Shadcn-UI library with its new update, blocks.
nextjs
typescript
shadcn-ui
clerk
tailwindcss
tailwind
shadcn
next
react
reactjs
## Next.js Installation
Let's begin by creating a new Next.js project using the `create-next-app` utility:
```bash title="terminal"
npx create-next-app@latest
```
During installation, you'll encounter the following prompts:
```terminal title="terminal"
√ What is your project named? ... my-app
√ Would you like to use TypeScript? ... No / _Yes_
√ Would you like to use ESLint? ... No / _Yes_
√ Would you like to use Tailwind CSS? ... No / _Yes_
√ Would you like to use `src/` directory? ... _No_ / Yes
√ Would you like to use App Router? (recommended) ... No / _Yes_
√ Would you like to customize the default import alias (@/*)? ... No / _Yes_
√ What import alias would you like configured? ... ~/*
```
Once you complete the prompts, `create-next-app` will establish a new project directory under your chosen project name and install the necessary dependencies. After the project setup is complete, navigate to your project directory:
```bash title="terminal"
cd my-app
```
## Install Shadcn-UI
Next, let’s incorporate the `shadcn-ui` library into our project. Execute the `shadcn-ui` initialization command to configure your project:
```bash title="terminal"
npx shadcn-ui@latest init
```
You will be prompted to answer several questions to configure the `components.json` file:
```terminal title="terminal"
√ Which style would you like to use? » _Default_
√ Which color would you like to use as base color? » _Neutral_
√ Would you like to use CSS variables for colors? ... no / _yes_
✔ Writing components.json...
✔ Initializing project...
✔ Installing dependencies...
Success! Project initialization completed. You may now add components.
```
## Install @clerk/nextjs
Now, we'll add the `clerk` library, a vital component for user authentication. The Clerk Next.js SDK includes prebuilt components, React hooks, and helpers to simplify the integration of user authentication. Add this SDK to our project:
```bash title="terminal"
npm install @clerk/nextjs
```
Before proceeding further, test the project by starting the development server with the following command:
```bash title="terminal"
npm run dev
```
If you navigate to http://localhost:3000, you should see your application running.
Go to the [Clerk website](https://clerk.com/), and sign in using your preferred method. Create your `<SignIn />`, choose your "Application name," and select your "Sign in options," then click on the "Create Application" button. You will be directed to a page where you can select your framework. Choose Next.js and follow the instructions provided:
### Set your environment variables
Add the following keys to your `.env.local` file. You can retrieve these keys from the API Keys section of your Clerk Dashboard.
```env title=".env.local"
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_*********************************************
CLERK_SECRET_KEY=sk_test_******************************************
```
### Add Middleware to your application
`clerkMiddleware()` provides access to the user authentication state throughout your application on any route or page. It also enables you to protect specific routes from unauthenticated users. To implement `clerkMiddleware()`, follow these steps:
Create a `middleware.ts` file and place it in the root directory alongside `.env.local.` In this file, export Clerk's `clerkMiddleware()` helper:
```ts title="middleware.ts"
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
```
By default, `clerkMiddleware()` does not protect any routes. You must opt-in to protection for specific routes.
### Add `<ClerkProvider>` and components to your app
To make Clerk's session and user context data available throughout your app, incorporate the `<ClerkProvider>` component into your `layout.tsx` file:
```tsx title="layout.tsx" {4,19,23}
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ClerkProvider } from "@clerk/nextjs";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<ClerkProvider>
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
</ClerkProvider>
);
}
```
Using Clerk's prebuilt components, you can easily control the visibility of content for users depending on their authentication state. Start by creating a user-friendly header that allows users to sign in or out. Here's how you can implement this:
`<SignedIn>`: This component displays its children only when the user is signed in.
`<SignedOut>`: This component displays its children only when the user is signed out.
`<UserButton />`: A stylish component that displays the user's avatar when signed in.
`<SignInButton />`: A basic component that links to the sign-in page or displays the sign-in modal.
Let's update our `layout.tsx` file to include the above components:
```tsx title="layout.tsx" {6-9, 28-36}
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import {
ClerkProvider,
SignInButton,
SignedIn,
SignedOut,
UserButton,
} from "@clerk/nextjs";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<ClerkProvider>
<html lang="en">
<body className={inter.className}>
<header>
<SignedOut>
<SignInButton />
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</header>
<main>{children}</main>
</body>
</html>
</ClerkProvider>
);
}
```
After making these changes, visit http://localhost:3000 while signed out to see the sign-in button. Once signed in, the user button will appear.
## Create Custom Sign-In and Sign-Up Pages
Craft personalized sign-in and sign-up pages for your Next.js app with Clerk.
### Develop a Sign-Up Page
First, create a new directory within the app folder named `auth`. Inside, create a subdirectory called `sign-up` and another within it named `[[...sign-up]]`. Within this last directory, create a `page.tsx` file and include the `<SignUp />` component from `@clerk/nextjs` to render the sign-up page.
```title="app/auth/sign-up/[[...sign-up]]/page.tsx"
📁 app
├ 📁 auth
. └ 📁 sign-up
. └ 📁 [[...sign-up]]
. └ 📄 page.tsx
```
```tsx title="page.tsx"
import { SignUp } from "@clerk/nextjs";
export default function SignUpPage() {
return <SignUp path="/auth/sign-up" />;
}
```
### Develop a Sign-In Page
Follow a similar structure for the `sign-in` page by creating a new file within the same directory structure, importing the `<SignIn />` component from `@clerk/nextjs`.
```title="app/auth/sign-in/[[...sign-in]]/page.tsx"
📁 app
├ 📁 auth
│ ├ 📁 sign-up
│ │ └ 📁 [[...sign-up]]
│ │ └ 📄 page.tsx
. └ 📁 sign-in
. └ 📁 [[...sign-in]]
. └ 📄 page.tsx
```
```tsx title="page.tsx"
import { SignIn } from "@clerk/nextjs";
export default function Page() {
return <SignIn path="/auth/sign-in" />;
}
```
### Update Your Environment Variables
In the previous steps, a path prop is passed to the `<SignIn />` and `<SignUp />` components. This is because the components need to know which route they are originally mounted on.
In Next.js applications, you can either pass the path prop, or you can define the `NEXT_PUBLIC_CLERK_SIGN_IN_URL` and `NEXT_PUBLIC_CLERK_SIGN_UP_URL` environment variables, like so:
```env title=".env.local"
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/auth/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/auth/sign-up
```
### Visit Your New Pages
Access your newly created pages locally at http://localhost:3000/auth/sign-in and http://localhost:3000/auth/sign-up.
## Styling Components and Pages
Now that we have everything set up, let's style our components and pages to enhance their appeal and usability for users.
### Navbar Component
First, we'll tackle the `navbar` component. Before we begin, we need to install the `Button` component from the `shadcn-ui` library:
```bash title="terminal"
npx shadcn-ui@latest add button
```
The command above will add the `Button` component to your project, which you can find in the `components`/`ui` directory.
Next, create a new file called `navbar.tsx` in the `components` folder.
```tsx title="navbar.tsx"
import { SignInButton, SignedIn, SignedOut, UserButton } from "@clerk/nextjs";
import { Button } from "~/components/ui/button";
export function Navbar() {
return (
<nav className="sticky z-40 top-0 backdrop-blur-xl bg-background/50">
<div className="max-w-screen-lg w-full flex-1 mx-auto px-6 flex items-center justify-between h-24">
<div className="text-lg font-semibold hover:opacity-70">App logo</div>
<SignedOut>
<Button asChild>
<SignInButton />
</Button>
</SignedOut>
<SignedIn>
<UserButton />
</SignedIn>
</div>
</nav>
);
}
```
We also need to import it into our `layout.tsx` file and clean up any previous changes by removing the header component and its imports, as we've now created our own:
```tsx title="layout.tsx" {5, 23}
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ClerkProvider } from "@clerk/nextjs";
import { Navbar } from "~/components/navbar";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<ClerkProvider>
<html lang="en">
<body className={inter.className}>
<Navbar />
<main>{children}</main>
</body>
</html>
</ClerkProvider>
);
}
```
### Authentication Page
With the navbar complete, let's move on to styling our authentication page using the `shadcn-ui` library, which provides ready-to-use code blocks. I highly recommend checking it out, as it greatly accelerates development time. Visit [shadcn-ui blocks →](https://ui.shadcn.com/blocks). For this task, we will use the `authentication-04` block.
Begin by creating an authentication component in the `components` directory alongside `navbar.tsx` and name it `auth.tsx`.
```title="components/auth.tsx" {3}
📁 components
├ 📁 ui
├ 📄 auth.tsx
├ 📄 navbar.tsx
```
```tsx title="auth.tsx"
import Image from "next/image";
import Link from "next/link";
import { ReactNode } from "react";
export function Auth({ children }: { children: ReactNode }) {
return (
<div className="absolute top-0 left-0 right-0 bottom-0 z-50 w-full max-h-screen flex overflow-hidden bg-background">
<Link href="/" className="absolute top-6 left-8">
← Home
</Link>
<div className="w-full lg:grid lg:min-h-[600px] lg:grid-cols-2 xl:min-h-[800px]">
<div className="flex items-center justify-center py-12">{children}</div>
<div className="hidden bg-muted lg:block">
<Image
src="/placeholder.svg"
alt="Image"
width="1920"
height="1080"
className="h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
/>
</div>
</div>
</div>
);
}
```
We also need to create a `placeholder.svg` file and place it in the `public` directory. You can replace this with any image of your choice; just remember to update the `placeholder.svg` in the `Image` component to reflect your chosen file name.
```title="public/placeholder.svg" {3}
📁 public
├ 📄 next.svg
├ 📄 placeholder.svg
├ 📄 vercel.svg
```
```svg title="placeholder.svg"
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none">
<rect width="1200" height="1200" fill="#EAEAEA" rx="3"/>
<g opacity=".5">
<g opacity=".5">
<path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/>
<path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/>
</g>
<path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/>
<path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/>
<path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/>
<path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/>
<path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/>
<g clip-path="url(#e)">
<path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/>
</g>
<path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/>
</g>
<defs>
<linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse">
<stop stop-color="#C9C9C9" stop-opacity="0"/>
<stop offset=".208" stop-color="#C9C9C9"/>
<stop offset=".792" stop-color="#C9C9C9"/>
<stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/>
</linearGradient>
<linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse">
<stop stop-color="#C9C9C9" stop-opacity="0"/>
<stop offset=".208" stop-color="#C9C9C9"/>
<stop offset=".792" stop-color="#C9C9C9"/>
<stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/>
</linearGradient>
<linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse">
<stop stop-color="#C9C9C9" stop-opacity="0"/>
<stop offset=".208" stop-color="#C9C9C9"/>
<stop offset=".792" stop-color="#C9C9C9"/>
<stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/>
</linearGradient>
<linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse">
<stop stop-color="#C9C9C9" stop-opacity="0"/>
<stop offset=".208" stop-color="#C9C9C9"/>
<stop offset=".792" stop-color="#C9C9C9"/>
<stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/>
</linearGradient>
<clipPath id="e">
<path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/>
</clipPath>
</defs>
</svg>
```
Now, let's wrap our `sign-in` (app/auth/sign-in/[[...sign-in]]/page.tsx) and `sign-up` (app/auth/sign-up/[[...sign-up]]/page.tsx) components with our newly created `auth.tsx` component:
```tsx title="page.tsx" {3, 7, 9}
import { SignIn } from "@clerk/nextjs";
import { Auth } from "~/components/auth";
export default function Page() {
return (
<Auth>
<SignIn path="/auth/sign-in" />
</Auth>
);
}
```
```tsx title="page.tsx" {3, 7, 9}
import { SignUp } from "@clerk/nextjs";
import { Auth } from "~/components/auth";
export default function SignUpPage() {
return (
<Auth>
<SignUp path="/auth/sign-up" />
</Auth>
);
}
```
## Conclusion
And that's it! It's as simple as that. I hope you found this tutorial helpful. Happy coding!
Loading...