更新vercel.json配置后应用出现404错误,求排查方案
问题背景
按照Vercel技术支持建议更新vercel.json以设置函数maxDuration参数后,应用出现404错误,切换到FastAPI也存在同样问题。
配置文件对比
更新前的vercel.json
{"version": 2,"builds": [{ "src": "chat.py", "use": "@vercel/python" }],"routes": [{ "src": "/(.*)", "dest": "chat.py" }],"functions": {"chat.py": {"maxDuration": 200}}}
更新后的vercel.json
{"routes": [{"src": "/api/message","dest": "/api/chat.py"}],"functions": {"api/chat.py": {"maxDuration": 200}}}
相关代码
Flask后端代码
from flask import Flask, request, jsonify import os import time from openai import OpenAI # Load environment variables from dotenv import load_dotenv load_dotenv() app = Flask(__name__) # Assistant ID and Configuration ASSISTANT_ID = "Must have TSSC" RUN_STATUS_CHECK_INTERVAL = 0.5 # seconds RUN_STATUS_MAX_ATTEMPTS = 600 # Initialize OpenAI client client = OpenAI() def handle_run_status(run, thread_id): for _ in range(RUN_STATUS_MAX_ATTEMPTS): run = client.beta.threads.runs.retrieve( thread_id=thread_id, run_id=run.id, ) # Check the status of the run and return early if completed or failed if run.status == "completed": return run elif run.status == "failed": print(f"Run failed: {run.last_error}") return run elif run.status not in ["queued", "in_progress", "requires_action"]: print(f"Run ended with status: {run.status}") return run # Wait for a short interval before checking again time.sleep(RUN_STATUS_CHECK_INTERVAL) # If the run did not complete in the expected time, log the event and return the run object print("Run did not complete in expected time.") return run def submit_message(assistant_id, thread_id, user_message): try: print(f"Submitting message to thread: {user_message}") client.beta.threads.messages.create( thread_id=thread_id, role="user", content=user_message ) print("Creating run...") run = client.beta.threads.runs.create( thread_id=thread_id, assistant_id=assistant_id, ) return run except Exception as e: print(f"Error in submit_message: {e}") raise def get_response(thread_id): try: # Retrieve messages, ensuring they are in ascending order response = client.beta.threads.messages.list(thread_id=thread_id, order="asc") print(f"Retrieved messages: {response}") # Log the retrieved messages # Check if response is empty or not as expected if not response.data: raise ValueError("No data in response") # Retrieve the latest assistant response # Iterate in reverse to find the latest assistant message for msg in reversed(response.data): if msg.role == "assistant": for content in msg.content: if content.type == 'text': assistant_response = content.text.value print('\nAssistant output: ' + str(assistant_response) + '\n') return assistant_response # Raise an error if no assistant response is found raise ValueError("No assistant responses found") except Exception as e: print(f"Error in get_response: {e}") raise @app.route('/message', methods=['POST']) def chat(): user_message = request.json.get('message') thread_id = request.json.get('thread_id', None) try: if not thread_id: print("Creating new thread...") thread = client.beta.threads.create() thread_id = thread.id else: print(f"Retrieving thread: {thread_id}") run = submit_message(ASSISTANT_ID, thread_id, user_message) run = handle_run_status(run, thread_id) if run.status == 'completed': assistant_response = get_response(thread_id) else: assistant_response = "Assistant did not respond in time or run failed." print("Sending response:", {'response': assistant_response, 'thread_id': thread_id}) return jsonify({'response': assistant_response, 'thread_id': thread_id}) except Exception as e: print(f"Error in chat: {e}") return jsonify({'error': str(e)}) if __name__ == '__main__': app.run(debug=True)
Next.js路由转发代码
import { NextRequest, NextResponse } from "next/server"; export async function POST(request: NextRequest) { try { console.log("Received request:", request); // Log incoming request // Forward the request to the Python backend console.log("Forwarding request to backend"); const backendRes = await fetch( "https://rooted-gpt-chat-script.vercel.app/api/message", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(await request.json()), } ); console.log("Received response from backend"); // Log backend response if (!backendRes.ok) { console.error("Error from backend:", backendRes.status); // Log backend error throw new Error(`Error from backend: ${backendRes.status}`); } // Get the response from the backend const data = await backendRes.json(); console.log("Backend data:", data); // Log the data from backend // Send the response back to the frontend return NextResponse.json(data); } catch (error) { console.error("Request failed:", error); // Log error return new NextResponse( JSON.stringify({ message: "Internal Server Error" }), { status: 500 } ); } } export const runtime = "edge";
排查思路
文件路径匹配检查
更新后的配置将函数路径改为api/chat.py,需确认项目中是否存在api目录且chat.py确实位于该目录下。Vercel函数路由依赖文件实际位置,路径不匹配会直接返回404。若原chat.py在根目录,需移动到api目录,或修改vercel.json中的dest路径为根目录的chat.py。路由规则与接口路径对齐
Flask后端的接口路径是/message,但Vercel配置中将路由指向/api/message并映射到/api/chat.py,此时后端实际暴露的接口应为/api/message,而非根路径的/message。需确认Next.js转发的地址是否正确,同时检查Flask代码是否需要调整路由前缀为/api/message。可直接在Postman中访问https://<your-vercel-domain>/api/message,手动发送POST请求测试是否能触发函数。Build配置缺失问题
更新后的vercel.json移除了builds字段,Vercel可能无法正确识别Python函数的构建规则。需重新添加builds配置:"builds": [{ "src": "api/chat.py", "use": "@vercel/python" }]没有
builds规则,Vercel可能不会将chat.py部署为Serverless函数,导致访问时返回404。Vercel部署日志排查
登录Vercel控制台,查看最新部署的日志,检查是否有构建错误或函数部署失败的提示。例如文件找不到、依赖安装失败等问题,都会导致函数无法正常部署进而返回404。函数运行时与路径验证
确认Vercel上的函数是否成功部署:进入项目的Functions页面,查看api/chat.py是否存在且状态正常。检查函数的触发路径是否与配置的/api/message一致,Vercel有时会自动调整路由规则,需以控制台显示的实际触发路径为准。Next.js转发地址的环境变量处理
当前Next.js代码中硬编码了后端地址,部署到Vercel后,可使用环境变量(如NEXT_PUBLIC_BACKEND_URL)替代硬编码地址,避免因域名变化导致的访问错误。同时确认该地址在Vercel环境中是否可访问(Edge Runtime可能有网络访问限制)。
内容的提问来源于stack exchange,提问作者mickmedical

