I’m experiencing an issue with NextAuth in my Next.js application. The session status
gets stuck at the loading
state, and the spinner appears indefinitely in the main navigation bar, but only on the home page.
Setup:
I have a <MainNav />
component in my app, as shown below:
main-nav.tsx
import React from "react"
import Link from "next/link"
import { MainNavItem } from "@/types"
import { cn } from "@/lib/utils"
import { UserAccountNav } from "@/components/user-account-nav"
interface MainNavProps {
items?: MainNavItem[]
children?: React.ReactNode
}
export function MainNav({ items }: MainNavProps) {
return (
<div className="flex h-16 items-center px-2.5 py-4 md:px-4">
{items?.length ? (
<nav className="ml-6 mr-auto hidden md:block">
<ul className="flex space-x-4">
{items.map((item) => (
<li
key={item.title}
className={cn(
"nav-link relative z-10 cursor-pointer py-0.5 font-medium text-muted-foreground transition-all hover:text-primary sm:text-sm"
)}
>
<Link href={item.href}>{item.title}</Link>
</li>
))}
</ul>
</nav>
) : null}
<div className="ml-auto flex items-center gap-4">
<Link
href="/contact"
className="text-sm font-medium text-muted-foreground"
>
Contact
</Link>
<UserAccountNav />
</div>
</div>
)
}
In the <MainNav />
, I render the <UserAccountNav />
component, as shown below:
user-account-nav.tsx
"use client"
import React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { signOut, useSession } from "next-auth/react"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Icons } from "@/components/icons"
import { UserAvatar } from "@/components/user-avatar"
export function UserAccountNav() {
const { data: session, status } = useSession()
const pathname = usePathname()
const name = session?.user?.name
const image = session?.user?.image
console.log("Auth status: ", status)
if (status === "loading") {
return (
<Icons.loaderCircle className="size-4 animate-spin text-muted-foreground" />
)
}
return (
<div className="w-max space-x-2">
{status === "authenticated" ? (
<DropdownMenu>
<DropdownMenuTrigger className="flex items-center space-x-1">
<UserAvatar
user={{ name: name || "", image: image || "" }}
className="size-8"
/>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<div className="flex items-center justify-start gap-2 p-2">
<div className="flex flex-col space-y-1 leading-none">
{session.user.email && (
<p className="w-[200px] truncate text-sm text-muted-foreground">
{session.user.email}
</p>
)}
</div>
</div>
<DropdownMenuSeparator />
{/* Menu items */}
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer"
onSelect={(event) => {
event.preventDefault()
signOut({
callbackUrl: `${window.location.origin}/login`,
})
}}
>
<Icons.logOut className="mr-2 size-4 text-muted-foreground" />
Sign out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
// Conditionally render the Login button only if the pathname is not '/login'
pathname !== "/login" && (
<Link
href="/login"
className={cn(buttonVariants({ size: "sm" }), "rounded-full")}
>
Log in
</Link>
)
)}
</div>
)
}
In the <UserAccountNav />
component:
- When the status is
loading
, I render a loading spinner. - When the status is
authenticated
, I render a<UserAvatar />
component. - When the status is
unauthenticated
, I render aLogin
link.
Here is the <NextAuthProvider />
component:
next-auth-provider.tsx
"use client"
import * as React from "react"
import { SessionProvider } from "next-auth/react"
type NextAuthProviderProps = {
children: React.ReactNode
}
export function NextAuthProvider({ children }: NextAuthProviderProps) {
return <SessionProvider>{children}</SessionProvider>
}
And here is the root layout of my app:
/app/layout.tsx
import "@/styles/globals.css"
import { Inter } from "next/font/google"
import NextTopLoader from "nextjs-toploader"
import { Toaster } from "@/components/ui/toaster"
import { NextAuthProvider } from "@/components/next-auth-provider"
import { ReactQueryProvider } from "@/components/react-query-provider"
const inter = Inter({ subsets: ["latin"] })
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" className={`${inter.className} scroll-smooth`}>
<body>
<ReactQueryProvider>
<NextAuthProvider>
<NextTopLoader showSpinner={false} color="#2563EB" />
{children}
<Toaster />
</NextAuthProvider>
</ReactQueryProvider>
</body>
<Toaster />
</html>
)
}
The Issue:
When I first load the home page, the session status
remains in loading
state and the spinner in the main nav is displayed indefinitely. To see this in action, visit https://indieaitools.com. In the main nav bar, to the right, you will see a loading spinner. Even if you refresh the page multiple times, you will still see the spinner.
Now, if you visit a different page, the loading status correctly updates. To see this in action, visit https://indieaitools.com/tools?name=MakeAudio, and you will see a Login
link in the main nav bar.
Now come back to the home page (by clicking on the Home link in the navbar), and you will see the Login
link. The session status
updates correctly. So, this problem appears only when I visit the home page for the first time.
Does anyone know why this might be happening or how to fix it?
Thanks in advance for your help!
Version Info:
- next: 14.1.3
- next-auth: ^4.24.5