Next.js 14 + Appwrite后端报错:TypeError: e._formData.get is not a function
解决Next.js 14 + Appwrite表单提交报错:
TypeError: e._formData.get is not a function 使用Next.js 14搭配Appwrite后端时,表单首次提交后无论成功与否都会触发TypeError: e._formData.get is not a function错误,刷新页面后后续提交仍持续报错。
相关代码
SignUpPage.tsx
import SignupForm from "./components/signupForm"; import axios from "axios"; const onSubmit = async (name: string, email: string, password: string) => { "use server"; console.log("SERVER name", name); console.log("SERVER email", email); console.log("SERVER password", password); const response = await fetch("http://localhost:3000/api/auth/signup", { method: "POST", headers: { "Content-Type": "application/JSON", }, body: JSON.stringify({ name, email, password, }), }); console.log("response", JSON.parse(JSON.stringify(response))); if (response.status === 200) { return JSON.parse(JSON.stringify(response)); } return { status: response.status, }; }; const SignUpPage = () => { return ( <div className="min-h-screen flex items-center justify-center bg-gray-100 px-6"> <div className="w-full max-w-md"> <h1 className="mb-6 text-center text-3xl font-bold text-gray-900"> Registro </h1> <SignupForm onSubmit={onSubmit} /> </div> </div> ); }; export default SignUpPage;
signupForm.tsx
"use client"; import { FormEvent } from "react"; import { useState } from "react"; const SignupForm = ({ onSubmit, }: { onSubmit: (name: string, email: string, password: string) => void; }) => { const [errorMsg, setErrorMsg] = useState(''); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (event: FormEvent<HTMLFormElement>) => { event.preventDefault(); const formData = new FormData(event.currentTarget); const email = formData.get("email") as string; const name = formData.get("name") as string; const password = formData.get("password") as string; setIsLoading(true); console.log("name", name); console.log("email", email); console.log("pass", password); try { const response = await onSubmit(name, email, password); console.log("response", response); } catch (error:any) { console.log('ERROR: ', error?.message); setErrorMsg(error?.message || 'ERROR'); }finally{ setIsLoading(false); } }; return ( <form onSubmit={handleSubmit} className="bg-white shadow-lg rounded-lg p-8"> <div className="mb-4"> <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="name" > Nombre </label> <input type="text" name="name" id="name" placeholder="Tu nombre completo" required className="shadow-sm bg-gray-50 border border-gray-300 rounded-md py-2 px-3 block w-full outline-none focus:ring-indigo-500 focus:border-indigo-500 text-gray-700" /> </div> <div className="mb-4"> <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="email" > Email </label> <input type="email" name="email" id="email" placeholder="tuemail@example.com" required className="shadow-sm bg-gray-50 border border-gray-300 rounded-md py-2 px-3 block w-full outline-none focus:ring-indigo-500 focus:border-indigo-500 text-gray-700" /> </div> <div className="mb-6"> <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="password" > Contraseña </label> <input type="password" name="password" id="password" placeholder="••••••••" required className="shadow-sm bg-gray-50 border border-gray-300 rounded-md py-2 px-3 block w-full outline-none focus:ring-indigo-500 focus:border-indigo-500 text-gray-700" /> </div> {errorMsg && <p className="error">{errorMsg}</p>} <div className="mb-4"> <button disabled={isLoading} type="submit" className="w-full py-2 px-4 bg-indigo-600 hover:bg-indigo-700 rounded-md shadow-sm text-white text-sm font-medium focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" > {isLoading ? 'Cargando...' : 'Registrar'} </button> </div> </form> ); }; export default SignupForm;
route.tsx
import { Account, Client, ID } from "appwrite"; import { revalidatePath } from "next/cache"; import { NextRequest, NextResponse } from "next/server"; type Payload = { email: string; name: string; password: string; }; export async function GET(request: NextRequest) { return NextResponse.json({ messsage: "Hello World" }); } export async function POST(request: NextRequest) { const body: Payload = await request.json(); const { email, name, password } = body; try { const client = new Client() .setEndpoint(`${process.env.APPWRITE_ENDPOINT}`) // Your API Endpoint .setProject(`${process.env.APPWRITE_PROJECT_ID}`); const account = new Account(client); console.log("account", JSON.stringify(account)); const result = await account.create(ID.unique(), email, password, name); const path = request.nextUrl.searchParams.get('path') || '/'; revalidatePath(path); return NextResponse.json({ status: 201, message: "Account Created", data: JSON.stringify(result), error: null, }); } catch (error: any) { console.error("Error:", error); if (error.code === 409) { return NextResponse.json({ status: 409, message: "A user with the same id, email, or phone already exists.", error: error.response, }); } return NextResponse.json({ status: error.code || 500, message: "An error occurred", error: error.response, }); } }
报错截图

问题根源
- 冗余的Server Action + API路由调用:你在Server Action中又调用了自身的API路由,不仅多此一举,还因为Server Action返回了序列化的Response对象,导致Next.js处理表单后续提交时出现FormData相关异常。
- 返回值处理不规范:直接序列化Response对象返回给客户端,不符合Server Action的返回规范,引发表单状态错乱。
修复方案
步骤1:简化Server Action,直接处理Appwrite逻辑
修改SignUpPage.tsx,去掉fetch调用,直接在Server Action中完成Appwrite用户创建:
import SignupForm from "./components/signupForm"; import { Account, Client, ID } from "appwrite"; const onSubmit = async (name: string, email: string, password: string) => { "use server"; console.log("SERVER name", name); console.log("SERVER email", email); console.log("SERVER password", password); try { const client = new Client() .setEndpoint(`${process.env.APPWRITE_ENDPOINT}`) .setProject(`${process.env.APPWRITE_PROJECT_ID}`); const account = new Account(client); const result = await account.create(ID.unique(), email, password, name); return { status: 201, message: "注册成功", data: result, error: null, }; } catch (error: any) { console.error("Error:", error); if (error.code === 409) { return { status: 409, message: "该邮箱已被注册", error: error.message, }; } return { status: error.code || 500, message: "注册失败", error: error.message, }; } }; const SignUpPage = () => { return ( <div className="min-h-screen flex items-center justify-center bg-gray-100 px-6"> <div className="w-full max-w-md"> <h1 className="mb-6 text-center text-3xl font-bold text-gray-900"> 注册 </h1> <SignupForm onSubmit={onSubmit} /> </div> </div> ); }; export default SignUpPage;
步骤2:修正客户端表单的类型定义与错误处理
修改signupForm.tsx,确保onSubmit类型为异步函数,并完善错误提示:
"use client"; import { FormEvent } from "react"; import { useState } from "react"; const SignupForm = ({ onSubmit, }: { onSubmit: (name: string, email: string, password: string) => Promise<{ status: number; message: string; data?: any; error?: string; }>; }) => { const [errorMsg, setErrorMsg] = useState(''); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (event: FormEvent<HTMLFormElement>) => { event.preventDefault(); const formData = new FormData(event.currentTarget); const email = formData.get("email") as string; const name = formData.get("name") as string; const password = formData.get("password") as string; setIsLoading(true); setErrorMsg(''); try { const response = await onSubmit(name, email, password); console.log("response", response); if (response.status === 201) { // 注册成功后可跳转至登录页 window.location.href = '/login'; } else { setErrorMsg(response.message); } } catch (error:any) { console.log('ERROR: ', error?.message); setErrorMsg(error?.message || '注册出错'); }finally{ setIsLoading(false); } }; return ( <form onSubmit={handleSubmit} className="bg-white shadow-lg rounded-lg p-8"> <div className="mb-4"> <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="name" > 姓名 </label> <input type="text" name="name" id="name" placeholder="请输入姓名" required className="shadow-sm bg-gray-50 border border-gray-300 rounded-md py-2 px-3 block w-full outline-none focus:ring-indigo-500 focus:border-indigo-500 text-gray-700" /> </div> <div className="mb-4"> <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="email" > 邮箱 </label> <input type="email" name="email" id="email" placeholder="your@email.com" required className="shadow-sm bg-gray-50 border border-gray-300 rounded-md py-2 px-3 block w-full outline-none focus:ring-indigo-500 focus:border-indigo-500 text-gray-700" /> </div> <div className="mb-6"> <label className="block text-gray-700 text-sm font-semibold mb-2" htmlFor="password" > 密码 </label> <input type="password" name="password" id="password" placeholder="••••••••" required className="shadow-sm bg-gray-50 border border-gray-300 rounded-md py-2 px-3 block w-full outline-none focus:ring-indigo-500 focus:border-indigo-500 text-gray-700" /> </div> {errorMsg && <p className="error text-red-500 text-sm mb-4">{errorMsg}</p>} <div className="mb-4"> <button disabled={isLoading} type="submit" className="w-full py-2 px-4 bg-indigo-600 hover:bg-indigo-700 rounded-md shadow-sm text-white text-sm font-medium focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" > {isLoading ? '加载中...' : '注册'} </button> </div> </form> ); }; export default SignupForm;
步骤3:删除冗余API路由(可选)
既然已经在Server Action中处理了Appwrite逻辑,app/api/auth/signup/route.tsx可以直接删除,避免代码冗余。
内容的提问来源于stack exchange,提问作者Jorgelig
相关产品推荐
相关产品推荐

