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

Next.js中如何仅首次加载获取用户数据?路由切换重复触发问题

Next.js 中实现仅首次加载获取用户数据的最优方案

问题根源

你遇到的重复触发请求、加载状态反复出现的问题,核心原因要么是AuthProvider没全局挂载(比如每个页面都单独套了一层),要么是初始化控制逻辑没彻底拦住重复执行。你用useRef标记初始化的思路是对的,但需要结合全局挂载和状态优化来落地。

解决步骤

1. 先把AuthProvider全局唯一挂载

把AuthProvider放在根layout.tsx里,确保整个应用只初始化一次。如果根layout是服务端组件,直接导入客户端的AuthProvider就行:

// app/layout.tsx
import { AuthProvider } from '@/components/AuthProvider';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="pt-BR">
      <body>
        <AuthProvider>{children}</AuthProvider>
      </body>
    </html>
  );
}

2. 优化AuthProvider的初始化与状态逻辑

修改你的AuthProvider代码,强化初始化控制,同时优化loading展示的时机:

'use client'

import { User } from '@/models/user'
import { AuthService } from '@/services/auth-service'
import {
  createContext,
  ReactNode,
  useCallback,
  useEffect,
  useRef,
  useState,
} from 'react'
import { setCookie, destroyCookie, parseCookies } from 'nookies'
import { useRouter } from 'next/navigation'
import http from '@/utils/axios'
import { UserService } from '@/services/user-service'
import { jwtDecode } from 'jwt-decode'
import { toast } from 'sonner'

interface AuthContextType {
  isAuthenticated: boolean
  user: User | null
  signIn: (email: string, password: string) => Promise<void>
  signOut: () => void
  isLoadingInitial: boolean // 专门标记首次加载状态
}

interface AuthProviderProps {
  children: ReactNode
}

export const AuthContext = createContext({} as AuthContextType)

export const AuthProvider = ({ children }: AuthProviderProps) => {
  const router = useRouter()
  const hasFetchedUser = useRef(false) // 标记是否已经获取过用户数据
  const [user, setUser] = useState<User | null>(null)
  const [isLoadingInitial, setIsLoadingInitial] = useState(true) // 只管控首次启动的加载

  const isAuthenticated = !!user

  const signIn = async (email: string, password: string) => {
    const response = await AuthService.login(email, password)

    const decodedToken = jwtDecode(response.token)

    if (!decodedToken.exp) {
      toast('Erro ao fazer login')
      return
    }

    destroyCookie(undefined, 'token')

    const tokenExpiration = (decodedToken?.exp * 1000 - Date.now()) / 1000

    setCookie(undefined, 'token', response.token, {
      maxAge: tokenExpiration,
    })

    http.defaults.headers.Authorization = `Bearer ${response.token}`

    setUser(response.user)
    router.replace('/rece')
  }

  const signOut = useCallback(() => {
    destroyCookie(undefined, 'token')
    destroyCookie(undefined, 'refreshToken')
    setUser(null)
    router.replace('/login')
  }, [router])

  useEffect(() => {
    // 已经获取过用户数据就直接返回,不再执行
    if (hasFetchedUser.current) return
    hasFetchedUser.current = true

    const initAuth = async () => {
      try {
        const cookies = parseCookies()
        const token = cookies['token']

        if (token) {
          http.defaults.headers.Authorization = `Bearer ${token}`
          const me = await UserService.getMe()
          if (me) setUser(me)
        }
      } catch (error) {
        // 处理token无效的情况,直接清掉过期token并提示
        destroyCookie(undefined, 'token')
        toast('Sessão expirada, faça login novamente')
      } finally {
        // 不管成功失败,都结束初始加载状态
        setIsLoadingInitial(false)
      }
    }

    initAuth()
  }, [])

  // 只有首次加载时才显示Loading,路由切换时不会再触发
  if (isLoadingInitial) {
    return <>Loading</>
  }

  return (
    <AuthContext.Provider value={{ isAuthenticated, user, signIn, signOut, isLoadingInitial }}>
      {children}
    </AuthContext.Provider>
  )
}

3. 关键优化点说明

  • 全局挂载:确保AuthProvider只在根layout里挂载一次,路由切换时不会重新初始化。
  • useRef持久化标记:hasFetchedUser会一直保留状态,哪怕组件重渲染也不会重置,彻底拦住重复请求。
  • 拆分loading状态:专门用isLoadingInitial管控首次启动的加载,路由切换时不再显示加载提示,提升流畅性。
  • 错误处理:初始化时捕获接口错误,自动清理无效token,避免用户停留在无效状态。

额外提示

  • 登录成功后直接用接口返回的用户数据就行,不用再调用getMe,减少一次请求。
  • 如果要处理token过期,可以加个refresh token机制,初始化时自动刷新有效token,不用用户重新登录。

内容的提问来源于stack exchange,提问作者Nilton Schumacher F

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:17:03