NextJS/Typescript调用API遇405错误,路由配置有误?
问题:Next.js 13+ 表单提交Beehiiv API出现405错误
环境与背景
基于src/app目录结构的Next.js 13+ + TypeScript项目,自定义邮件订阅表单对接Beehiiv新闻通讯API,测试cURL请求可正常完成订阅,但网页表单提交时(本地/线上环境)均返回405 Method Not Allowed错误。
API路由代码(原文件:src/app/api/subscribe.js)
import axios from "axios"; export default async function handler(req, res) { if (req.method === "POST") { try { const { email, firstName, lastName } = req.body; // 假设客户端会传入firstName和lastName const postData = { email, reactivate_existing: false, send_welcome_email: false, utm_source: "WayneEnterprise", utm_campaign: "fall_2022_promotion", utm_medium: "organic", referring_site: "www.wayneenterprise.com/blog", custom_fields: [ { name: "First Name", value: firstName }, { name: "Last Name", value: lastName } ] }; const response = await axios.post( "https://api.beehiiv.com/v2/publications/pub_abc/subscriptions", postData, { headers: { Authorization: "Bearer xyz", // 替换为实际API密钥 "Content-Type": "application/json", }, } ); res.status(200).json(response.data); } catch (error) { res.status(error.response ? error.response.status : 500).json({ message: error.message }); } } else { res.setHeader("Allow", ["POST"]); res.status(405).end(`Method ${req.method} Not Allowed`); } }
测试通过的cURL请求
(base) reallymemorable@Christians-MacBook-Pro repo % curl --request POST \ --url https://api.beehiiv.com/v2/publications/pub_abc/subscriptions \ --header 'Accept: application/json' \ --header 'Authorization: Bearer xyz' \ --header 'Content-Type: application/json' \ --data '{ "email": "jane.wayne@wayneenterprise.com", "reactivate_existing": false, "send_welcome_email": false, "utm_source": "WayneEnterprise", "utm_campaign": "fall_2022_promotion", "utm_medium": "organic", "referring_site": "www.wayneenterprise.com/blog", "custom_fields": [ { "name": "First Name", "value": "Jane" }, { "name": "Last Name", "value": "Wayne" } ] }' {"data":{"id":"sub_asdfgh","email":"jane.wayne@wayneenterprise.com","status":"validating","created":1704370796,"subscription_tier":"free","utm_source":"","utm_medium":"","utm_channel":"","utm_campaign":"","referring_site":"","referral_code":"3fS4ea7yg6","stripe_customer_id":"","custom_fields":[]}}%
前端表单组件代码(src/app/components/newsletter-form.tsx)
"use client"; import { EnvelopeIcon } from "@heroicons/react/20/solid"; import axios from "axios"; import React, { useState } from "react"; interface SubmitProps { pending: boolean; } function Submit({ pending }: SubmitProps) { return ( <button type="submit" className="button-primary hover:bg-black focus-visible:outline-primary my-2 w-full px-3.5 py-2.5 text-base font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2" disabled={pending} > {pending ? "Submitting..." : "Stay Informed"} </button> ); } export default function NewsLetterForm() { const [pending, setPending] = useState<boolean>(false); const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); setPending(true); const email = ( event.currentTarget.elements.namedItem("email") as HTMLInputElement ).value; try { const response = await axios.post("/api/subscribe", { email }); console.log("Subscription successful:", response.data); // 处理订阅成功逻辑(比如给用户提示) } catch (error) { console.error("Subscription error:", error); // 处理错误逻辑(比如给用户提示错误) } finally { setPending(false); } }; return ( <section className="border border-[#313233] p-0.5"> <div className="p-2 border border-dashed border-gray-400"> <form onSubmit={handleSubmit}> <div className="justify-between flex"> <div> <span className="small-header font-semibold"> Weekly Digest </span> </div> <div> <span className="text-xs"><a href="https://news.mynews.com/" target="_blank" className="tertiary-link" rel="noopener noreferrer">Archive</a></span> </div> </div> <div className="relative mt-2 shadow-sm"> <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"> <EnvelopeIcon className="h-5 w-5 text-gray-400" aria-hidden="true" /> </div> <input type="email" name="email" id="email" required autoComplete="email" className="block w-full border-0 py-1.5 pl-10 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-[#188F9B] sm:text-sm sm:leading-6" placeholder="you@email.com" /> </div> <Submit pending={pending} /> </form> </div> </section> ); }
排查与解决方案
1. 核心问题:Next.js App Router 路由命名规范错误
Next.js 13+的App Router要求API路由必须放在app/api/[route-name]/route.ts(或.js)文件中,而不是直接在app/api下创建单个文件。原文件src/app/api/subscribe.js不符合规范,导致Next.js无法识别该API端点,任何请求都会返回405错误。
修复步骤:
- 创建目录
src/app/api/subscribe - 将原
subscribe.js移动到该目录下,并重命名为route.ts - 修改路由代码的导出方式,App Router使用单独的HTTP方法导出(而非默认导出handler),示例代码:
import axios from "axios"; import { NextResponse } from "next/server"; export async function POST(request: Request) { try { const { email } = await request.json(); // 前端未传入姓名字段,暂时移除custom_fields中的对应项,避免undefined const postData = { email, reactivate_existing: false, send_welcome_email: false, utm_source: "WayneEnterprise", utm_campaign: "fall_2022_promotion", utm_medium: "organic", referring_site: "www.wayneenterprise.com/blog", custom_fields: [] }; const response = await axios.post( "https://api.beehiiv.com/v2/publications/pub_abc/subscriptions", postData, { headers: { Authorization: "Bearer xyz", // 替换为实际API密钥 "Content-Type": "application/json", }, } ); return NextResponse.json(response.data); } catch (error: any) { return NextResponse.json( { message: error.message }, { status: error.response?.status || 500 } ); } }
2. 数据匹配问题
原API路由中尝试解构firstName和lastName,但前端表单仅收集了email,会导致这两个值为undefined。虽然不直接引发405,但可能导致Beehiiv API返回错误,建议:
- 要么在表单中添加姓名输入字段,并在
handleSubmit中一并传入 - 要么在API路由中处理这些字段的缺失情况(如示例代码中暂时留空
custom_fields)
3. 请求路径验证
前端的请求路径/api/subscribe是正确的,无需修改。只要路由文件结构符合规范,该路径即可正确访问到API端点。
内容的提问来源于stack exchange,提问作者reallymemorable
相关产品推荐
相关产品推荐

