Fastify Cookie在Next.js生产环境部署后无法持久化问题排查
环境与技术栈
- 后端:Fastify+TRPC,部署在Render免费层
- 管理后台:Next.js 14(App Router),部署在Vercel免费层
- 移动端:Kotlin Multiplatform
- 认证流程:Firebase登录后,后端设置
__Secure-myproject.auth-tokenCookie,用于接口鉴权和Next.js中间件维持登录状态,用户上下文由Zustand管理
问题现象
- 本地开发(Next.js:
localhost:3000,服务端:localhost:8080)Cookie流程完全正常 - 生产环境登录成功后,Cookie首次存在,但刷新页面后消失,导致无法跳转到
/home,中间件因检测不到Token重定向回登录页
相关代码片段
Cookie配置
export const cookieConfig = { authTokenName: '__Secure-myproject.auth-token', default: { maxAge: 7 * 24 * 60 * 60, httpOnly: EnvConfig.NODE_ENV === 'production', secure: EnvConfig.NODE_ENV === 'production', sameSite: 'none', path:'/' } satisfies FastifyCookieOptions['parseOptions'] }
服务端CORS与Cookie配置
server.register(fastifyCors, { origin: [ 'http://localhost:3000', 'https://mybackendprojectname.onrender.com', 'https://vercel.app', 'https://myadminpanelname.vercel.app/' ], allowedHeaders: ['Content-Type', 'Authorization'], methods: ['GET', 'POST', 'OPTIONS', 'PATCH', 'PUT', 'DELETE'], credentials: true }) server.register(fastifyCookie, { secret: EnvConfig.JWT_AUTH_SECRET, parseOptions: cookieConfig.default, logLevel: 'warn', hook: 'onRequest' })
Next.js 中间件
export function middleware(request: NextRequest) { const authToken = (request.cookies.get('__Secure-myproject.auth-token') ?.value as unknown as string) ?? '' const authRoutes = ['/', '/forgot-password'] const publicRoutes = ['/faq'] if (publicRoutes.includes(request.nextUrl.pathname)) { return NextResponse.next() } if (authRoutes.includes(request.nextUrl.pathname)) { if (authToken) { return NextResponse.redirect( new URL(authConfig.defaultAuthRedirect, request.url) ) } return NextResponse.next() } if (!authToken) { return NextResponse.redirect( new URL(authConfig.defaultUnathRedirect, request.url) ) } return NextResponse.next() } export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon.ico).*)' ] }
登录组件代码
const GoogleLoginButton = () => { const router = useRouter() const { setUser } = useAuthStore() const { mutateAsync: doGoogleLoginVerification } = trpcClient.auth0.googleLogin.useMutation({}) const handleGoogleLogin = async () => { try { // firebase returns info object with token and other things const userCreds = await LoginWithGoogle() // extract id token from res object const idToken = await userCreds.user.getIdToken() // send id token to server so admin sdk can verify const user = await doGoogleLoginVerification({ idtoken: idToken }) setUser({ id: user.id, name: user.name, email: user.email, authToken: user.authToken }) router.replace('/home') } catch (error: any) { console.log('Google Login Error ::', error) } } return ( // Login button ui..... ) }
排查方向
CORS Origin 精准匹配
- Vercel域名末尾不要加斜杠:将
https://myadminpanelname.vercel.app/改为https://myadminpanelname.vercel.app,浏览器会严格校验Origin,带斜杠和不带斜杠会被视为不同来源 - 移除泛域名
https://vercel.app,只保留实际的管理后台域名,避免CORS校验范围过大导致的问题
- Vercel域名末尾不要加斜杠:将
Cookie跨域属性校验
- 确认生产环境下
EnvConfig.NODE_ENV确实为production,保证httpOnly和secure属性正确生效 __Secure-前缀的Cookie强制要求HTTPS环境,确认Render和Vercel的服务均为HTTPS协议- 跨域场景下,检查是否需要添加
domain属性:如果前后端域名不同,需明确设置Cookie的domain为前端域名或父域名(若存在)
- 确认生产环境下
TRPC请求Credentials配置
- 检查TRPC Client配置,确保跨域请求时开启
credentials: 'include',否则浏览器不会携带Cookie
- 检查TRPC Client配置,确保跨域请求时开启
Next.js中间件Cookie读取
- 确认中间件运行在Edge Runtime,该环境支持读取
httpOnlyCookie - 核对Cookie名称拼写完全一致,包括
__Secure-前缀的大小写
- 确认中间件运行在Edge Runtime,该环境支持读取
Render免费层休眠影响
- Render免费层实例长时间无请求会休眠,测试后端唤醒后的请求是否能正常携带Cookie
内容的提问来源于stack exchange,提问作者Jeet Patel
相关产品推荐
相关产品推荐

