Next.js 14 App目录登录设Cookie刷新触发Hydration Failed问题求助
一、错误原因
刷新页面触发Hydration Failed,核心是服务器端渲染(SSR)与客户端Hydration阶段的内容不一致:客户端设置Cookie后,当前页面的服务器渲染结果(未读取到Cookie)和客户端实际状态(已存在Cookie)冲突,导致页面结构不匹配。
二、解决方案
1. 客户端设置Cookie的优化
使用js-cookie设置Cookie后,必须立即跳转页面,让服务器重新渲染新页面(此时服务器能读取到新Cookie),避免当前页面的Hydration冲突:
'use client' import Cookies from 'js-cookie' import { useRouter } from 'next/navigation' const handleFinish = async (e: any) => { const res = await api.post('/Auth/user/signIn', e) if (!res || res.status !== 200) return Cookies.set('access_token', res.data.token, { secure: process.env.NODE_ENV === 'production', // 本地开发关闭secure,否则http环境下Cookie无法写入 expires: 12000, path: '/' // 确保全站可访问 }) toast.success(res.data.message, { position: 'bottom-center' }) router.replace('/') // 跳转页面,触发服务器重新渲染 }
2. 服务器组件读取Cookie
服务器组件通过headers()函数直接读取请求头中的Cookie:
import { headers } from 'next/headers' export default async function ServerPage() { const cookieStr = headers().get('cookie') let accessToken = null if (cookieStr) { const cookies = Object.fromEntries( cookieStr.split('; ').map(item => item.split('=')) ) accessToken = cookies.access_token } return <div>{accessToken ? '已登录' : '未登录'}</div> }
3. 客户端组件读取Cookie
客户端组件直接用js-cookie或document.cookie读取:
'use client' import Cookies from 'js-cookie' export default function ClientComponent() { const accessToken = Cookies.get('access_token') return <div>{accessToken ? '已登录' : '未登录'}</div> }
4. 避免Hydration冲突的关键规则
如果必须在当前页面根据登录状态渲染内容,需用useEffect在客户端Hydration完成后再读取Cookie,避免服务器与客户端渲染结果不一致:
'use client' import { useState, useEffect } from 'react' import Cookies from 'js-cookie' export default function LoginStatus() { const [isLoggedIn, setIsLoggedIn] = useState(false) useEffect(() => { const token = Cookies.get('access_token') setIsLoggedIn(!!token) }, []) return <div>{isLoggedIn ? '已登录' : '未登录'}</div> }
5. 更可靠的服务器端设置Cookie方案
将登录请求转发到Next.js API路由,在服务器端设置Cookie,确保服务器和客户端能同时获取到:
创建API路由 app/api/auth/signin/route.ts
import { NextResponse } from 'next/server' export async function POST(request: Request) { const body = await request.json() // 调用第三方登录接口 const authRes = await fetch(`${process.env.AUTH_API_BASE}/Auth/user/signIn`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) if (!authRes.ok) { return NextResponse.json({ error: '登录失败' }, { status: authRes.status }) } const data = await authRes.json() const response = NextResponse.json({ message: data.message }) // 服务器端设置Cookie response.cookies.set({ name: 'access_token', value: data.token, secure: process.env.NODE_ENV === 'production', expires: new Date(Date.now() + 12000 * 24 * 60 * 60 * 1000), // 转换为日期对象 path: '/', sameSite: 'strict' // 若不需要客户端读取Cookie,可添加 httpOnly: true 提升安全性 }) return response }
客户端调用该API路由
'use client' import { useRouter } from 'next/navigation' const handleFinish = async (e: any) => { const res = await fetch('/api/auth/signin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(e) }) if (!res.ok) return const data = await res.json() toast.success(data.message, { position: 'bottom-center' }) router.replace('/') }
内容的提问来源于stack exchange,提问作者Muhammad Yuldashev
相关产品推荐
相关产品推荐

