I’m developing a Next.js app that utilizes NextAuth.js (^4.24.7) for Google authentication. Everything works fine initially, but I’m encountering an issue where after I close the server in VS Code and then restart it, NextAuth.js gets stuck in a loading state, i have to manually refresh the page to make it work normally.
Here’s the scenario:
Start the Next.js server.
Log in with Google using NextAuth.js. Authentication works as expected.
Close the server in VS Code.
Restart the server.
NextAuth.js remains stuck in a loading state, even though the user is already logged in.
After a long time in loading, it gives this error: Chunk Load Error: Loading chunk app/layout failed.
(timeout: http://localhost:3000/%5C_next/static/chunks/app/layout.js)
Manually refreshing the page shows that the user is logged in and everything works fine.
Based on Chunk Load Error, I deleted the .next folder, cleared the browser cache but it was of no use.
Auth Options:
export const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
},
},
}),
],
callbacks: {
async signIn(params) {
try {
await dbConnection();
const profile = params.profile as GoogleProfile;
const userExists = await UserModel.findOne({ email: profile?.email });
if (!userExists) {
const email = profile?.email;
const atIndex = email.indexOf("@");
const username = email.slice(0, atIndex);
await UserModel.create({
email,
name: profile?.name,
profilePicture: profile?.picture,
username,
});
}
return true;
} catch (error) {
console.log(error);
return false;
}
},
async session({ session }) {
await dbConnection();
const user = await UserModel.findOne({ email: session.user.email });
session.user._id = user?._id.toString();
session.user.email = user?.email;
session.user.profilePicture = user?.profilePicture;
session.user.username = user?.username;
return session;
},
},
};
Route:
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
LAYOUT:
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${inter.className} flex flex-col min-h-screen`}>
<Providers>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<Toaster richColors position="top-center" />
<Navbar />
{children}
<Footer />
</ThemeProvider>
</Providers>
</body>
</html>
);
}
Root Page:
export default function Home() {
const { data: session, status } = useSession();
useEffect(() => {
if (!session) {
console.log("session is not present");
} else {
console.log("session is present");
}
console.log("user", session?.user);
}, [session, status]);
return (<div>..Static Landing Page..</div>)
}
Provider:
import { SessionProvider } from "next-auth/react";
export const Providers = ({ children }: { children: React.ReactNode }) => {
return <SessionProvider>{children}</SessionProvider>;
};
Navbar:
"use client";
// imports
const Navbar = () => {
const { data: session, status } = useSession();
const router = useRouter();
useEffect(() => {
if (status === "loading" || status === "authenticated") {
return;
}
if (!session) {
router.push("/");
return;
}
}, [session, status, router]);
return (
<div className="sticky top-0 z-10 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:bg-gray-200 lg:dark:bg-zinc-800/30 py-3">
<div className="flex justify-between items-center container px-1 xs:px-4">
<div
onClick={() => router.push("/")}
className="text-2xl font-bold flex gap-4 hover:cursor-pointer"
>
<Image
src="/Jarno-Single-engine-Cessna.svg"
alt="Vercel Logo"
width={100}
height={24}
className="-scale-x-100 -rotate-12"
/>
<p className="hidden sm:flex">Title</p>
</div>
<div className="flex items-center gap-4">
<ModeToggle />
{status === "authenticated" ? (
<DropdownMenuComponent />
) : (
<Button
disabled={status === "loading"}
className="disabled:bg-gray-400"
onClick={() => signIn()}
>
{status === "loading" ? "Processing" : "Login"}
</Button>
)}
</div>
</div>
</div>
);
};
export default Navbar;
Dropdown Menu Component:
export function DropdownMenuComponent() {
const { data: session, status } = useSession();
let profilePicture = session?.user?.profilePicture;
const router = useRouter();
useEffect(() => {
if (status === "loading") {
return;
} else if (status === "authenticated") {
profilePicture = session?.user?.profilePicture;
return;
} else {
router.push("/");
return;
}
}, [session, status]);
return (
<div>drop down content</div>
);
}
The loading state in Navbar is what keeps on loading.