Next.js 13中如何跨组件共享已登录用户数据?
实现Next.js 13全局共享登录用户数据及状态同步
一、后端先补充用户信息接口
因为HttpOnly Cookie前端无法直接读取,必须通过后端接口验证JWT并返回用户数据。在NestJS里新增一个接口:
// src/auth/auth.controller.ts import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Controller('api/auth') export class AuthController { // 用JWT Guard验证Cookie中的token @Get('me') @UseGuards(AuthGuard('jwt')) getCurrentUser(@Req() req) { // req.user是JWT验证后解析出的用户数据,返回前端需要的字段 return { id: req.user.id, username: req.user.username, email: req.user.email }; } }
注意要确保你的JWT策略已经配置成从Cookie中提取token(默认是从Authorization头,需要调整):
// src/auth/jwt.strategy.ts import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { constructor() { super({ jwtFromRequest: ExtractJwt.fromExtractors([ // 从Cookie中提取JWT,这里的cookie名要和登录接口设置的一致 (req) => req.cookies?.access_token ]), ignoreExpiration: false, secretOrKey: process.env.JWT_SECRET, }); } async validate(payload: any) { return { id: payload.sub, username: payload.username, email: payload.email }; } }
二、前端全局状态管理(App Router)
用React Context实现轻量全局状态,同步Cookie的有效性:
1. 创建Auth上下文
新建app/context/AuthContext.tsx,记得加'use client'指令:
'use client'; import { createContext, useContext, useEffect, useState } from 'react'; type User = { id: string; username: string; email: string; } | null; type AuthContextType = { user: User; fetchUser: () => Promise<void>; logout: () => Promise<void>; }; const AuthContext = createContext<AuthContextType | undefined>(undefined); export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState<User>(null); // 调用后端接口获取用户信息 const fetchUser = async () => { try { const res = await fetch('/api/auth/me', { credentials: 'include', // 必须带这个,否则Cookie不会被发送 }); if (res.ok) { const data = await res.json(); setUser(data); } else { // 401或其他错误,说明Cookie无效/过期 setUser(null); } } catch (err) { setUser(null); } }; // 登出逻辑:调用后端接口清除Cookie,然后重置用户状态 const logout = async () => { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include', }); setUser(null); }; // 页面加载时自动获取用户状态 useEffect(() => { fetchUser(); }, []); return ( <AuthContext.Provider value={{ user, fetchUser, logout }}> {children} </AuthContext.Provider> ); } // 自定义钩子,简化上下文调用 export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }
2. 在根布局中包裹应用
修改app/layout.tsx,用AuthProvider包裹所有子组件:
import { AuthProvider } from './context/AuthContext'; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="zh-CN"> <body> <AuthProvider>{children}</AuthProvider> </body> </html> ); }
三、在组件中使用用户状态
比如导航栏显示欢迎语:
'use client'; import { useAuth } from '../context/AuthContext'; export default function Navbar() { const { user, logout } = useAuth(); return ( <nav> <div> {user ? ( <> <span>欢迎回来,{user.username}!</span> <button onClick={logout}>登出</button> </> ) : ( <a href="/login">登录</a> )} </div> </nav> ); }
个人页面验证登录状态:
'use client'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; import { useAuth } from '../context/AuthContext'; export default function ProfilePage() { const { user } = useAuth(); const router = useRouter(); useEffect(() => { if (!user) { router.push('/login'); } }, [user, router]); if (!user) { return <div>加载中...</div>; } return ( <div> <h1>个人主页</h1> <p>用户名:{user.username}</p> <p>邮箱:{user.email}</p> </div> ); }
四、处理Cookie过期/删除的情况
- 统一接口拦截:封装一个带认证的fetch工具,自动处理401状态:
'use client'; import { useAuth } from './context/AuthContext'; export function useAuthFetch() { const { fetchUser } = useAuth(); const authFetch = async (url: string, options: RequestInit = {}) => { const res = await fetch(url, { ...options, credentials: 'include', }); if (res.status === 401) { // Cookie过期/无效,重新获取用户状态(会设为null) await fetchUser(); } return res; }; return authFetch; }
之后所有需要认证的接口都用这个钩子调用,比如获取用户帖子:
'use client'; import { useEffect, useState } from 'react'; import { useAuthFetch } from '../hooks/useAuthFetch'; import { useAuth } from '../context/AuthContext'; type Post = { id: string; title: string; content: string; }; export default function UserPosts() { const { user } = useAuth(); const authFetch = useAuthFetch(); const [posts, setPosts] = useState<Post[]>([]); useEffect(() => { if (!user) return; const getPosts = async () => { const res = await authFetch(`/api/users/${user.id}/posts`); if (res.ok) { const data = await res.json(); setPosts(data); } }; getPosts(); }, [user, authFetch]); return ( <div> <h2>我的帖子</h2> {posts.map(post => ( <div key={post.id}> <h3>{post.title}</h3> <p>{post.content}</p> </div> ))} </div> ); }
- 定时校验状态:如果需要更高的实时性,可以在全局上下文里加定时任务,比如每隔5分钟调用一次
fetchUser,检查Cookie有效性(注意不要过于频繁,避免接口压力)。
五、服务器端验证(可选)
如果需要在服务器组件中验证用户状态,可以用Next.js的cookies()函数获取Cookie,然后调用后端接口:
// app/profile/page.tsx(服务器组件) import { redirect } from 'next/navigation'; import { cookies } from 'next/headers'; export default async function ProfilePage() { const cookieStore = cookies(); const accessToken = cookieStore.get('access_token'); if (!accessToken) { // 未登录,重定向到登录页 return redirect('/login'); } // 调用后端接口验证并获取用户信息 const res = await fetch('http://your-nestjs-api/api/auth/me', { headers: { Cookie: `access_token=${accessToken.value}`, }, cache: 'no-store', }); if (!res.ok) { return redirect('/login'); } const user = await res.json(); return ( <div> <h1>个人主页</h1> <p>用户名:{user.username}</p> </div> ); }
内容的提问来源于stack exchange,提问作者matheusosan
相关产品推荐
相关产品推荐

