部署至Vercel/AWS Amplify时出现[CLIENT_FETCH_ERROR] Unexpected token错误
问题:部署NextAuth到Vercel/AWS Amplify后出现[CLIENT_FETCH_ERROR] Unexpected token错误
本地运行完全正常,但部署到Vercel或AWS Amplify后,出现**[CLIENT_FETCH_ERROR] Unexpected token**错误。
Vercel环境变量
NODE_ENV="production" NEXTAUTH_URL="vercel_url" NEXTAUTH_SECRET="VZsVRu8vqgkiqz53GrohF/EQS59pN1m46nVunlTemXA=" BACKEND="django_api_url" JWT_SECRET="JmG3h0sKkM0DlUbIPybA0QvtqKuxLex7Wqa9btciNXU="
NEXTAUTH_SECRET和JWT_SECRET均通过openssl生成。
[...nextauth].ts代码
import NextAuth from 'next-auth' import type { NextAuthOptions } from 'next-auth' import CredentialsProvider from 'next-auth/providers/credentials' import axios, { AxiosResponse } from 'axios' import { AuthResponse, combinedJWT } from './types' const refreshAccessToken = async (token: combinedJWT) => { const response: AxiosResponse<{ access: string }, any> = await axios.post( `${process.env.BACKEND}/api/token/refresh/`, { refresh: token.refreshToken, } ) if (response.status === 200 && response.data.access) { console.log('access: ' + response.data.access) return { ...token, accessToken: response.data.access, expiresAt: Date.now() + 5000, } } return { ...token, error: 'RefreshAccessTokenError', } } export const authOptions = { providers: [ CredentialsProvider({ name: 'Credentials', credentials: { otp: { label: 'otp', type: 'text' }, userId: { label: 'UserId', type: 'text' }, }, async authorize(credentials) { const user: AxiosResponse<AuthResponse> = await axios.post( `${process.env.BACKEND}/auth/login/${credentials?.userId}/`, { otp: credentials?.otp } ) if (user.status === 200) { return { id: user.data.id, username: user.data.username, email: user.data.email, user_type: user.data.user_type, tokens: user.data.tokens, } } else { return null } }, }), ], secret: process.env.JWT_SECRET, pages: { signIn: '/signin', }, callbacks: { async jwt({ token, user }: any) { if (user) { return { ...token, accessToken: user.tokens.access, refreshToken: user.tokens.refresh, expiresAt: Date.now() + 5000, userType: user.user_type, name: user.username, email: user.email, userId: user.id, } } if (Date.now() < token.expiresAt) { return token } const newToken = await refreshAccessToken(token) return newToken }, async session({ session, token, user }: any) { session.accessToken = token.accessToken if (user) { session.user = { ...user, id: token.userId, name: token.name, user_type: token.userType, } } else { session.user = { id: token.userId, name: token.name, user_type: token.userType, } } return session }, }, debug: process.env.NODE_ENV === 'development', } as NextAuthOptions export default NextAuth(authOptions)
Vercel运行时日志
2022-10-30T12:27:31.291Z 057bedbb-fa76-45dc-8f6b-1b352da0d3b5 ERROR [next-auth][error][CLIENT_FETCH_ERROR] https://next-auth.js.org/errors#client_fetch_error invalid json response body at https://ob-client-v2.vercel.app/api/auth/session reason: Unexpected token < in JSON at position 0 { error: { message: 'invalid json response body at https://ob-client-v2.vercel.app/api/auth/session reason: Unexpected token < in JSON at position 0', stack: 'FetchError: invalid json response body at https://ob-client-v2.vercel.app/api/auth/session reason: Unexpected token < in JSON at position 0\n' + ' at /var/task/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' + ' at processTicksAndRejections (node:internal/process/task_queues:96:5)', name: 'FetchError' }, url: 'https://ob-client-v2.vercel.app/api/auth/session', message: 'invalid json response body at https://ob-client-v2.vercel.app/api/auth/session reason: Unexpected token < in JSON at position 0' }
解决方案
- 修正NEXTAUTH_URL配置:将环境变量中的
NEXTAUTH_URL替换为实际的Vercel部署域名(比如https://ob-client-v2.vercel.app),占位符会导致NextAuth生成错误的会话端点,请求返回HTML错误页面而非JSON。 - 验证后端API可访问性:确认
BACKEND指向的Django API在生产环境中能被Vercel服务器访问,检查API的CORS设置是否允许Vercel域名的请求。 - 检查NextAuth的secret配置:虽然你用了
JWT_SECRET作为secret,但建议使用官方推荐的NEXTAUTH_SECRET,同时确保环境变量中的值没有多余引号或格式错误。 - 调整令牌过期时间:当前代码中
expiresAt设置为5秒后过期,会导致频繁触发令牌刷新,建议改为与后端JWT实际过期时间匹配的值(如15分钟:Date.now() + 15 * 60 * 1000),避免因频繁请求导致后端返回错误。 - 确认API路由部署状态:在Vercel控制台检查
/api/auth/相关路由是否成功部署,无构建错误,若路由未部署会返回404 HTML页面,引发JSON解析失败。
内容的提问来源于stack exchange,提问作者χerфmiи
相关产品推荐
相关产品推荐

