NextJS中如何将Middleware的登录状态传递至客户端组件?
问题描述
我正在为Web应用构建身份验证功能,在middleware.ts中实现了判断用户是否登录的逻辑,并提取出以下函数:
export const checkIsLoggedIn = (req: any): boolean => { return !!req.auth; };
由于Middleware属于服务端逻辑,而组件是客户端逻辑,我尝试通过API路由(check-auth.ts)将登录状态传递给main-nav.tsx组件,但浏览器控制台返回错误:
[main-nav-tsx][checkAuthStatus]: Error fetching auth status SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
错误提示API路由未找到,目前无法将登录状态传递到组件,所有尝试的方法均无效,目录结构也已确认无误。我需要一种可靠的方式判断用户是否登录,从而按需显示登出按钮,也接受基于会话或令牌的新解决方案,但暂无相关使用经验。
相关代码文件
src/middleware.ts
// import { auth } from "./auth" import authConfig from "./auth.config" import NextAuth from "next-auth" import { DEFAULT_LOGIN_REDIRECT, apiAuthPrefix, authRoutes, publicRoutes, } from "@/routes" import { NextResponse } from "next/server"; const { auth } = NextAuth(authConfig); export const checkIsLoggedIn = (req: any): boolean => { return !!req.auth; }; export default auth((req) => { const { nextUrl } = req; const isLoggedIn = !!req.auth; console.log('[middleware-ts][checkisloggedin]: ', checkIsLoggedIn(req)); console.log('[middleware-ts][isloggedin]: ', isLoggedIn); const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix); const isPublicRoute = publicRoutes.includes(nextUrl.pathname); const isAuthRoute = authRoutes.includes(nextUrl.pathname); if (isApiAuthRoute) { return NextResponse.next(); } if (isAuthRoute) { if (isLoggedIn) { return NextResponse.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl)) } return NextResponse.next(); } if (!isLoggedIn && !isPublicRoute) { return NextResponse.redirect(new URL("/auth/login", nextUrl)); } return NextResponse.next(); }) export const config = { // matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"] , // matcher: ["/auth/login", "/auth/register"], matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'], }
src/components/main-nav.tsx
"use client" import * as React from "react" import Link from "next/link" import { usePathname } from "next/navigation" import { docsConfig } from "@/config/docs" import { siteConfig } from "@/config/site" import { cn } from "@/lib/utils" import { Icons } from "@/components/icons" import { signOut } from "@/auth" export function MainNav() { const pathname = usePathname() const [isLoggedIn, setIsLoggedIn] = React.useState(false); React.useEffect(() => { const checkAuthStatus = async () => { console.log('[main-nav-tsx][checkAuthStatus]: Checking auth status...'); try { const response = await fetch('/app/api/authentication/check-auth'); if (response.ok) { const data = await response.json(); console.log('[main-nav-tsx][checkAuthStatus]: Auth status response:', data); setIsLoggedIn(data.isLoggedIn); } else { console.error('[main-nav-tsx][checkAuthStatus]: Error fetching auth status', response.status); } } catch (error) { console.error('[main-nav-tsx][checkAuthStatus]: Error fetching auth status', error); } } checkAuthStatus(); }, []); /** * handleSignOut is a server side action to check for log in * main-nav is a client side action * even though the method is post, we cannot use server action for checking if logged in is true or false * hence a POST api is needed */ const handleSignOut = async () => { const response = await fetch('/app/api/authentication/signout', { method: 'POST', }); if (response.ok) { // Handle successful sign-out, e.g., redirect or update state setIsLoggedIn(false); } else { // Handle error console.error('Error signing out'); } }; return ( <div className="mr-4 hidden md:flex"> <Link href="/" className="mr-6 flex items-center space-x-2"> <Icons.logo className="h-6 w-6" /> <span className="hidden font-bold sm:inline-block"> {siteConfig.name} </span> </Link> <nav className="flex items-center gap-4 text-sm lg:gap-6"> {docsConfig.mainNav?.map(item => ( item.href && ( <Link key={item.href} href={item.href} className={cn( "transition-colors hover:text-foreground/80", pathname === item.href ? "text-foreground" : "text-foreground/60" )} > {item.title} </Link> ) ))} {/* change this button else where */} {isLoggedIn && ( <button onClick={handleSignOut}>Sign Out</button> )} </nav> </div> ) }
src/app/api/authentication/check-auth.ts
import { NextApiRequest, NextApiResponse } from 'next' import { checkIsLoggedIn } from '../../../middleware' export default function handler(req: NextApiRequest, res: NextApiResponse) { const isLoggedIn = checkIsLoggedIn(req); // console.log("[check-auth-ts]: ", isLoggedIn) res.status(200).json({ isLoggedIn }); }
解决方案
1. 修正API路由路径与文件结构
- 路径错误:组件中请求的
/app/api/authentication/check-auth是错误的,Next.js App Router的API路由根路径是/api,正确请求路径应为/api/authentication/check-auth。 - 文件名要求:App Router的API路由必须命名为
route.ts,而非check-auth.ts,需将原文件重命名为src/app/api/authentication/check-auth/route.ts。
2. 修复API路由逻辑
Middleware的req与API路由的req类型不同,直接复用checkIsLoggedIn会导致req.auth不存在。改用NextAuth官方提供的getServerSession方法获取会话状态:
// src/app/api/authentication/check-auth/route.ts import { getServerSession } from "next-auth/next"; import authConfig from "@/auth.config"; import { NextResponse } from "next/server"; export async function GET() { const session = await getServerSession(authConfig); return NextResponse.json({ isLoggedIn: !!session }); }
3. 组件中修正请求路径
将main-nav.tsx中的请求路径改为正确的API路由地址:
const response = await fetch('/api/authentication/check-auth');
4. 更优方案:直接使用NextAuth客户端钩子
无需自定义API路由,NextAuth提供了useSession钩子可在客户端组件直接获取登录状态,更简洁可靠:
// src/components/main-nav.tsx "use client" import * as React from "react" import Link from "next/link" import { usePathname } from "next/navigation" import { docsConfig } from "@/config/docs" import { siteConfig } from "@/config/site" import { cn } from "@/lib/utils" import { Icons } from "@/components/icons" import { signOut, useSession } from "next-auth/react" export function MainNav() { const pathname = usePathname() const { data: session } = useSession() const isLoggedIn = !!session const handleSignOut = async () => { await signOut({ redirect: false }) }; return ( <div className="mr-4 hidden md:flex"> <Link href="/" className="mr-6 flex items-center space-x-2"> <Icons.logo className="h-6 w-6" /> <span className="hidden font-bold sm:inline-block"> {siteConfig.name} </span> </Link> <nav className="flex items-center gap-4 text-sm lg:gap-6"> {docsConfig.mainNav?.map(item => ( item.href && ( <Link key={item.href} href={item.href} className={cn( "transition-colors hover:text-foreground/80", pathname === item.href ? "text-foreground" : "text-foreground/60" )} > {item.title} </Link> ) ))} {isLoggedIn && ( <button onClick={handleSignOut}>Sign Out</button> )} </nav> </div> ) }
内容的提问来源于stack exchange,提问作者Wer Wer
相关产品推荐
相关产品推荐

