DEV Community

Yash Matrani
Yash Matrani

Posted on

# Stop Breaking Your Next.js App: NextAuth Setup in App Router (with MongoDB)

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.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)