Next.js部署Vercel时调用API出现401未授权错误求助
Next.js部署Vercel后API接口返回401未授权问题排查
问题背景
本地运行Next.js应用时一切正常,但部署到Vercel后,调用/api/posts接口始终返回401未授权错误。
相关代码文件
app/api/posts/route.js
// app/api/posts/route.js import { NextResponse } from 'next/server'; export async function GET() { return NextResponse.json({ items: [{id:1, title:"Hello World"}]}) } export async function POST() { return NextResponse.json({ hello: "abc"}) }
app/blog/page.js
// app/blog/page.js import getData from "@/app/lib/getData" export default async function BlogPage() { const data = await getData() const items = data && data.items ? [...data.items] : [] console.log(data) console.log(process.env.NEXT_PUBLIC_VERCEL_ENV) console.log(process.env.NEXT_PUBLIC_VERCEL_URL) return ( <main> <h1>Blog</h1> <p>posts:</p> {items && items.map((item, idx)=>{ return <li key={`post-${idx}`}>{item.title}</li> })} </main> ) }
app/lib/getDomain.js
// app/lib/getDomain.js export default function getDomain() { console.log(process.env.NEXT_PUBLIC_VERCEL_ENV) console.log(process.env.NEXT_PUBLIC_VERCEL_URL) const protocol = process.env.NEXT_PUBLIC_VERCEL_ENV === "production" ? "https" : "http" const domain = process.env.NEXT_PUBLIC_VERCEL_URL ? process.env.NEXT_PUBLIC_VERCEL_URL : "localhost:3000" return `${protocol}://${domain}` }
app/lib/getData.js
// app/lib/getData.js import getDomain from "@/app/lib/getDomain" export default async function getData() { const domain = getDomain() const endpoint = `${domain}/api/posts` console.log(endpoint) const res = await fetch(endpoint) console.log(res) if (!res.ok) { throw new Error('Failed to fetch data') } if (res.headers.get('content-type') !== 'application/json') { return {items:[]} } return res.json() }
控制台日志
production my-vercel-123abc-projects.vercel.app https://my-vercel-123abc-projects.vercel.app/api/posts production my-vercel-123abc-projects.vercel.app https://my-vercel-123abc-projects.vercel.app/api/posts Response { [Symbol(realm)]: null, [Symbol(state)]: { aborted: false, rangeRequested: false, timingAllowPassed: true, requestIncludesCredentials: true, type: 'default', status: 401, timingInfo: { startTime: 1758.9642749999948, redirectStartTime: 0, redirectEndTime: 0, postRedirectStartTime: 1758.9642749999948, finalServiceWorkerStartTime: 0, finalNetworkResponseStartTime: 0, finalNetworkRequestStartTime: 0, endTime: 0, encodedBodySize: 0, decodedBodySize: 0, finalConnectionTimingInfo: null }, cacheState: '', statusText: 'Unauthorized', headersList: HeadersList { cookies: [Array], [Symbol(headers map)]: [Map], [Symbol(headers map sorted)]: null }, urlList: [ [URL] ], body: { stream: undefined, length: undefined, source: undefined } }, [Symbol(headers)]: HeadersList { cookies: [ '_vercel_sso_nonce=KDscsdfgregvgVderlP3YmHxS; Max-Age=3600; Path=/; Secure; HttpOnly; SameSite=Lax' ], [Symbol(headers map)]: Map(11) { 'cache-control' => [Object], 'connection' => [Object], 'content-length' => [Object], 'content-type' => [Object], 'date' => [Object], 'server' => [Object], 'set-cookie' => [Object], 'strict-transport-security' => [Object], 'x-frame-options' => [Object], 'x-robots-tag' => [Object], 'x-vercel-id' => [Object] }, [Symbol(headers map sorted)]: null } }
问题分析与解决方案
核心原因
你在getData.js中通过完整域名的外部HTTP请求调用同应用内的API接口,在Vercel环境下,这种请求会触发平台的SSO验证机制,导致返回401错误。而本地开发环境没有该验证逻辑,所以运行正常。
另外,Next.js App Router的服务器组件可以直接调用业务逻辑,不需要通过HTTP请求,这才是更高效的正确做法。
修复步骤
抽离公共业务逻辑
创建app/lib/posts.js,把API路由中的业务逻辑抽成独立函数:// app/lib/posts.js export async function getPosts() { return { items: [{id:1, title:"Hello World"}] }; } export async function createPost() { return { hello: "abc" }; }更新API路由
修改app/api/posts/route.js,调用抽离后的公共函数:// app/api/posts/route.js import { NextResponse } from 'next/server'; import { getPosts, createPost } from '@/app/lib/posts'; export async function GET() { const data = await getPosts(); return NextResponse.json(data); } export async function POST() { const data = await createPost(); return NextResponse.json(data); }修改页面组件
更新app/blog/page.js,直接调用公共函数获取数据,不再通过fetch请求API:// app/blog/page.js import { getPosts } from "@/app/lib/posts" export default async function BlogPage() { const data = await getPosts() const items = data?.items || [] return ( <main> <h1>Blog</h1> <p>posts:</p> {items.map((item, idx)=>( <li key={`post-${idx}`}>{item.title}</li> ))} </main> ) }清理冗余文件
可以删除app/lib/getDomain.js和app/lib/getData.js,因为已经不需要通过外部请求获取数据。
额外说明
如果确实需要在客户端组件中调用API(比如用户交互场景),直接使用相对路径/api/posts即可,Vercel会正确处理内部路由,不会触发SSO验证:
// 客户端组件中使用 const res = await fetch('/api/posts');
内容的提问来源于stack exchange,提问作者Justin Bates
相关产品推荐
相关产品推荐

