You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 22:14:58