Next.js 13部署Vercel构建失败,求非Server Action的外部请求方案
我在Next.js 13的API路由中写了以下代码:
api/home/route.ts
import { NextResponse } from "next/server" import API from "@/lib/axios" export async function GET(req: Request, res: Response) { try { const response = await API.get("/landing") return NextResponse.json(response.data) } catch (error: any) { console.log(error) return NextResponse.json({ message: error.message }, { status: 500 }) } }
调用该API的代码如下:
lib/utils.ts
export async function fetchData(path: string) { let url if (process.env.ENVIRONMENT === "local") { url = "http://localhost:3000" + path } else if (process.env.NODE_ENV === "production") { // Production environment url = path } const response = await fetch(`${url}`) return await response.json() }
落地页中的使用方式:
import { fetchData } from "@/lib/utils" export default async function Home() { const data = await fetchData("/api/home") }
本地yarn dev运行正常,本地yarn build也能成功,但部署到Vercel时构建失败。另外联系页也用了类似方案:创建/api/contact路由接收POST请求,调用Laravel后端发邮件,该路由在用户提交表单时触发。
需要解决两个问题:
- 排查Vercel部署失败的原因
- 在不使用实验性Server Action的前提下,向外部服务器发起POST/GET请求的最佳方案
一、Vercel构建失败原因
部署失败的核心原因是构建阶段预渲染时请求了未启动的内部API路由:
Next.js 13 App Router的页面组件默认是服务端组件,构建阶段会预渲染页面。你的Home是async组件,构建时会执行fetchData("/api/home"),生产环境下直接请求/api/home,但Vercel构建环境是孤立的,此时API路由还未启动,导致请求失败。本地构建正常是因为本地环境可能还运行着开发服务器,和Vercel的构建逻辑存在差异。
二、优化方案(不使用Server Action)
方案1:跳过Next.js中转路由,直接调用外部API
既然/api/home只是中转层,直接在页面组件里调用后端接口即可,省去中间跳转:
// Home页面组件 import API from "@/lib/axios" export default async function Home() { try { const response = await API.get("/landing") const data = response.data // 渲染数据逻辑 } catch (error: any) { // 错误处理 } }
优势:减少请求链路,提升性能,彻底避免构建阶段的内部请求问题。
方案2:抽离业务逻辑,避免构建阶段HTTP请求
如果必须保留Next.js中转API(比如统一处理鉴权、请求头),可以把API路由的业务逻辑抽离成独立函数,页面和API路由直接调用该函数:
- 抽离逻辑到
lib/api/landing.ts:
import API from "@/lib/axios" export async function getLandingData() { try { const response = await API.get("/landing") return response.data } catch (error: any) { throw new Error(error.message) } }
- 修改
api/home/route.ts:
import { NextResponse } from "next/server" import { getLandingData } from "@/lib/api/landing" export async function GET() { try { const data = await getLandingData() return NextResponse.json(data) } catch (error: any) { return NextResponse.json({ message: error.message }, { status: 500 }) } }
- 修改页面组件:
import { getLandingData } from "@/lib/api/landing" export default async function Home() { const data = await getLandingData() // 渲染数据逻辑 }
这样页面直接调用逻辑函数,无需通过HTTP请求,规避了构建阶段的内部API访问问题。
方案3:POST请求场景(联系页表单)
对于用户交互触发的POST请求(比如联系表单提交),不会在构建阶段执行,现有API路由方案是可行的,只需确保请求处理逻辑正确:
- 完善
api/contact/route.ts:
import { NextResponse } from "next/server" import API from "@/lib/axios" export async function POST(req: Request) { try { const body = await req.json() const response = await API.post("/send-email", body) return NextResponse.json(response.data) } catch (error: any) { return NextResponse.json({ message: error.message }, { status: 500 }) } }
- 客户端调用(联系页组件):
async function handleSubmit(formData: FormData) { const res = await fetch("/api/contact", { method: "POST", body: JSON.stringify(Object.fromEntries(formData)), headers: { "Content-Type": "application/json" } }) const data = await res.json() // 处理响应逻辑 }
总结
- Vercel构建失败的核心是预渲染阶段请求未启动的内部API,解决思路是避免构建阶段发起内部HTTP请求,要么直接调用业务逻辑,要么跳过中转层。
- 客户端触发的POST请求(如表单提交),现有API路由方案可正常使用,只需确保请求处理逻辑正确。
内容的提问来源于stack exchange,提问作者psudo

