You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 23:34:51