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

NextJS14.2服务端子组件Dropdown登出后不动态刷新的解决

问题描述
  • 环境:NextJS v14.2、React v18.3
  • 通过layout.tsx全局挂载服务端组件Navbar,其包含基于ShadCN UI的服务端子组件Dropdown
  • 登录流程正常:用户登录后跳转至成员页面,Navbar/Dropdown会根据用户角色(管理员/普通用户)自动刷新内容
  • 登出异常:调用route.ts的GET接口完成登出并跳转至首页/后,控制台显示Navbar已触发,但Dropdown未更新为未登录状态(如显示“未登录用户”、isAdmin=false),仅手动刷新首页可恢复正常
  • 已尝试无效方案:在route.ts中添加revalidate('/')、在首页设置动态渲染headers

相关代码

import Link from 'next/link'

import './navbar.css'

import DropDown from './dropdown'
import GetUser from '@/app/components/getUser'

/*
NAVBAR

links to history, draw and members list pages

gets user from layout.tsx and forwards data to dropdown component

*/

type typeNavProps = {
    name: string,
    loggedEmail: string,
    isLoggedOn: boolean,
    isAdmin: boolean
}

export default async function Navbar() {
    const navBarProps: typeNavProps = await GetUser()

    console.log('Fired NavBar with user: ', navBarProps.name)

    return (
        <nav className="navbar">
            <div className="navbar__container">
                <Link href="/login" id="navbar__logo">The Slade</Link>
                <div className="navbar__toggle" id="mobile-menu">
                    <span className="bar"></span> <span className="bar"></span>
                    <span className="bar"></span>
                </div>
                <ul className="navbar__menu">
                    <li className="navbar__item">
                        <Link href="/history" className="navbar__links">History</Link>
                    </li>
                    <li className="navbar__item">
                        <Link href="/draw" className="navbar__links">Draw</Link>
                    </li>
                    <li className="navbar__item">
                        <Link href="/members" className="navbar__links">Members List</Link>
                    </li>
                    <li>
                        <DropDown
                            name={navBarProps.name}
                            loggedEmail={navBarProps.loggedEmail}
                            isLoggedOn={navBarProps.isLoggedOn}
                            isAdmin={navBarProps.isAdmin}
                        />
                    </li>
                </ul>
            </div>
        </nav>
    )
}
import Link from 'next/link'

import {
    DropdownMenu,
    DropdownMenuContent,
    DropdownMenuItem,
    //DropdownMenuLabel,
    //DropdownMenuSeparator,
    DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

type typeNavProps = {
    name: string,
    loggedEmail: string,
    isLoggedOn: boolean,
    isAdmin: boolean
}

/* 
DROPDOWN BOX  is from shadui
gets user data from layout.tsx via navbar using navBarProps
    displays name of user
    and cuts available options if not an admin
*/

export default function DropDown(navBarProps: typeNavProps) {

    console.log('Fired dropdown with props from navbar: ', navBarProps)

    if (navBarProps.isAdmin) {
        return (
            <>
                <DropdownMenu>
                    <DropdownMenuTrigger>{navBarProps.name}</DropdownMenuTrigger>
                    <DropdownMenuContent className='mt-10'>
                        <DropdownMenuItem>
                            <Link href="/login" >Log In</Link>
                        </DropdownMenuItem>
                        <DropdownMenuItem>
                            <Link href="/logout" >Log Out</Link>
                        </DropdownMenuItem>
                        <DropdownMenuItem>
                            <Link href="/updatemail" >Update Email</Link>
                        </DropdownMenuItem>
                        <DropdownMenuItem>
                            <Link href="/updatepassword" >Update Password</Link>
                        </DropdownMenuItem>
                        <DropdownMenuItem>
                            <Link href="/updateadmin" >Update Admin</Link>
                        </DropdownMenuItem>
                        <DropdownMenuItem>
                            <Link href="/updateactive" >Update Active</Link>
                        </DropdownMenuItem>
                        <DropdownMenuItem>
                            <Link href="/emailinvite" >Send Email Invitation</Link>
                        </DropdownMenuItem>
                    </DropdownMenuContent>
                </DropdownMenu>
            </>
        )
    } else {
        return (
            <>
                <DropdownMenu>
                    <DropdownMenuTrigger>{navBarProps.name}</DropdownMenuTrigger>
                    <DropdownMenuContent className='mt-10'>
                        <DropdownMenuItem>
                            <Link href="/login" >Log In</Link>
                        </DropdownMenuItem>
                        <DropdownMenuItem>
                            <Link href="/logout" >Log Out</Link>
                        </DropdownMenuItem>
                        <DropdownMenuItem>
                            <Link href="/updatemail" >Update Email</Link>
                        </DropdownMenuItem>
                        <DropdownMenuItem>
                            <Link href="/updatepassword" >Update Password</Link>
                        </DropdownMenuItem>
                    </DropdownMenuContent>
                </DropdownMenu>
            </>
        )
    }
}

route.ts(登出接口)

import { redirect } from "next/navigation"

import { createClient } from '@/app/utils/supabase/server'
import { NextApiRequest } from "next"

export async function GET(req: NextApiRequest) {
    console.log('logoutUser was fired with: ', req)

    const supabase = createClient()

    const { error } = await supabase.auth.signOut()
    if (error) {
        console.log('error logging out in logout route: ', error)
    }

    redirect('/')
}

解决方案

1. 修复登出路由的缓存清除逻辑

NextJS 14.2中revalidate('/')已被弃用,改用revalidatePath清除布局和首页的缓存,确保登出后服务端重新渲染组件:

import { redirect, revalidatePath } from "next/navigation"
import { createClient } from '@/app/utils/supabase/server'

export async function GET() {
    const supabase = createClient()
    const { error } = await supabase.auth.signOut()
    if (error) {
        console.log('error logging out in logout route: ', error)
    }
    // 清除首页和全局布局的缓存
    revalidatePath('/', 'layout')
    revalidatePath('/')
    redirect('/')
}

2. 强制Navbar动态渲染

服务端组件默认静态优化,可能复用旧缓存数据。给Navbar添加动态渲染指令,确保每次请求都重新获取用户状态:

// 在navbar.tsx顶部添加
export const dynamic = "force-dynamic"

export default async function Navbar() {
    // 原有代码不变
}

3. 修正GetUser函数的缓存策略

检查GetUser函数的缓存设置,确保每次都从Supabase拉取最新用户数据:

// 修改GetUser函数的缓存配置
export async function GetUser() {
    const supabase = createClient()
    // 禁用缓存,强制获取最新用户状态
    const { data: { user } } = await supabase.auth.getUser({
        cache: 'no-store'
    })
    // 原有逻辑处理name、isAdmin等字段
}

4. 客户端兜底刷新(可选)

如果服务端缓存问题仍存在,可将登出改为客户端操作,触发路由刷新:

// 替换Dropdown中的Logout链接为客户端组件按钮
'use client'
import { useRouter } from 'next/navigation'
import { createClientComponentClient } from '@/app/utils/supabase/client'
import { DropdownMenuItem } from "@/components/ui/dropdown-menu"

export default function LogoutButton() {
    const router = useRouter()
    const handleLogout = async () => {
        const supabase = createClientComponentClient()
        await supabase.auth.signOut()
        // 触发客户端路由刷新,重新获取服务端组件数据
        router.refresh()
    }
    return (
        <DropdownMenuItem onClick={handleLogout}>
            Log Out
        </DropdownMenuItem>
    )
}

内容的提问来源于stack exchange,提问作者SgtBilko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 10:42:04