Setting up authentication in Next.js 14 (App Router) is a nightmare if you don't understand the boundary between Server Components and Client Components.
I spent hours debugging useSession returning null or throwing hydration errors. Here is the exact, fool-proof way to set up NextAuth (Auth.js) with a MongoDB database.
1. Installation
bash
npm install next-auth @auth/mongodb-adapter mongodb
2. Environment Variables
Create a .env.local file and add your GitHub OAuth credentials and MongoDB URI:
env
GITHUB_ID=your_github_client_id
GITHUB_SECRET=your_github_client_secret
MONGODB_URI=your_mongodb_connection_string
NEXTAUTH_SECRET=any_long_random_string
NEXTAUTH_URL=http://localhost:3000
3. The MongoDB Adapter
To save users in your database, create a file lib/mongodb.js:
javascript
import { MongoClient } from "mongodb";
if (!process.env.MONGODB_URI) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
}
const uri = process.env.MONGODB_URI;
const options = {};
let client;
let clientPromise;
if (process.env.NODE_ENV === "development") {
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options);
global._mongoClientPromise = client.connect();
}
clientPromise = global._mongoClientPromise;
} else {
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
export default clientPromise;
4. The Auth Route Handler
In App Router, NextAuth requires a specific Route Handler. Create app/api/auth/[...nextauth]/route.js:
javascript
import NextAuth from "next-auth";
import GithubProvider from "next-auth/providers/github";
import { MongoDBAdapter } from "@auth/mongodb-adapter";
import clientPromise from "@/lib/mongodb";
export const authOptions = {
adapter: MongoDBAdapter(clientPromise),
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
session: {
strategy: "jwt",
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
5. The Client Component Wrapper
Because layout.js is a Server Component by default, you cannot put SessionProvider directly inside it. You need a wrapper.
Create app/providers.js:
javascript
"use client";
import { SessionProvider } from "next-auth/react";
export default function Providers({ children }) {
return <SessionProvider>{children}</SessionProvider>;
}
Now wrap your body in app/layout.js:
javascript
import Providers from "./providers";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
6. Using the Session (The Trap)
This is where 90% of people fail.
In Client Components:
javascript
"use client";
import { useSession } from "next-auth/react";
export default function Profile() {
const { data: session } = useSession();
if (!session) return <p>Not logged in</p>;
return <p>Welcome {session.user.name}</p>;
}
In Server Components / API Routes:
Never use useSession here. Use getServerSession.
javascript
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
export default async function ServerPage() {
const session = await getServerSession(authOptions);
if (!session) return <p>Unauthorized</p>;
return <p>Hello {session.user.email}</p>;
}
Conclusion
NextAuth is incredibly powerful once it clicks. The golden rule of App Router: Keep hooks in the client, and getServerSession in the server.
Stop wasting hours on hydration errors. Build.
Top comments (0)