Next.js 14使用formData()上传图片时出现TypeError: s is not a function错误
Next.js 图片上传报错
TypeError: s is not a function 问题排查与解决 项目概述
尝试实现图片上传至Next.js无服务后端:前端通过FormData组装数据,用axios POST到后端;后端从NextRequest获取formData,转成buffer后保存到服务器文件系统。
问题
点击前端发布按钮后,Next.js控制台抛出错误:TypeError: s is not a function
完整错误栈
TypeError: s is not a function at PATH\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:37:5830 at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async tX (PATH\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:37:5207) at async rl (PATH\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:38:22994) at async doRender (PATH\node_modules\next\dist\server\base-server.js:1407:30) at async cacheEntry.responseCache.get.routeKind (PATH\node_modules\next\dist\server\base-server.js:1571:28) at async DevServer.renderToResponseWithComponentsImpl (PATH\node_modules\next\dist\server\base-server.js:1479:28) at async DevServer.renderErrorToResponseImpl (PATH\node_modules\next\dist\server\base-server.js:2104:24) at async pipe.req.req (PATH\node_modules\next\dist\server\base-server.js:1982:30) at async DevServer.pipeImpl (PATH\node_modules\next\dist\server\base-server.js:902:25)
客户端代码(page.tsx)
"use client"; import React from "react"; import dynamic from "next/dynamic"; import "react-quill/dist/quill.snow.css"; import { useRouter } from "next/navigation"; import axios from "axios"; // 动态导入ReactQuill,确保仅在客户端加载 const ReactQuill = dynamic(() => import("react-quill"), { ssr: false }); var toolbarOptions = [ // 工具栏配置 ]; const BLOG_API = "/api/blogs"; export default function WriteBlogPage() { const router = useRouter(); const [title, setTitle] = React.useState(""); const [image, setImage] = React.useState<File>(); const [content, setContent] = React.useState(""); const [slug, setSlug] = React.useState(""); const handleContentChange = (value: any) => { setContent(value); }; const handleFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); setSlug(title.replaceAll(" ", "-").toLowerCase()); if (!image || !title || !content || !slug) return; try { const data = new FormData(); data.append("image", image, image.name); data.append("title", title); data.append("content", content); data.append("slug", slug); const response = await axios.post(BLOG_API, data); if (response.status === 200) { router.push("/"); } else { console.error("发布博客失败"); } } catch (error) { console.error("发布博客出错:", error); } }; return ( <form onSubmit={handleFormSubmit}> <div className="w-full"> <ReactQuill value={content} onChange={handleContentChange} modules={{ toolbar: toolbarOptions, }} className="text-center" placeholder="写下你的精彩内容" /> </div> <input type="file" accept="image/*" onChange={(e) => setImage(e.target.files?.[0])} /> <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="博客标题" /> <button type="submit"> 发布 </button> </form> ); }
服务端代码(修复后)
// 导入必要模块和模型 import { NextRequest, NextResponse } from "next/server"; import { dbConnect as connect } from "@/dbConfig/dbConfig"; import Blog from "@/models/blogModel"; import NodeCache from "node-cache"; import { join } from "path"; import { writeFile } from "fs/promises"; // 连接数据库 connect(); // NodeCache缓存配置 const cache = new NodeCache({ stdTTL: 1800, checkperiod: 300, }); // 创建新博客的POST路由 export async function POST(req: NextRequest) { try { const data = await req.formData(); const image: File | null = data.get("image") as File; const title: string | null = data.get("title") as string; const content: string | null = data.get("content") as string; const slug: string | null = data.get("slug") as string; if (!image || !title || !content || !slug) { return NextResponse.json({ success: false, message: "请填写所有必要字段", }, { status: 400 }); } const existingBlog = await Blog.findOne({ title }); if (existingBlog) { return NextResponse.json({ success: false, message: "该标题的博客已存在,不允许重复发布", }, { status: 409 }); } const bytes = await image.arrayBuffer(); const buffer = Buffer.from(bytes); // 拼接正确的服务器文件路径 const filePath = join(process.cwd(), "public", "uploads", "blogs", "cover", image.name); await writeFile(filePath, buffer); const coverImagePath = join("/uploads/blogs/cover", image.name); const blog = new Blog({ coverImagePath, title, content, slug, }); const savedBlog = await blog.save(); cache.del("blogs"); return NextResponse.json({ message: "博客发布成功", success: true, savedBlog, }, { status: 201 }); } catch (error: any) { return NextResponse.json({ error: error.message }, { status: 500 }); } }
已尝试方案
- 使用
NextApiRequest/NextApiResponse:提示Property 'formData' does not exist on type 'NextApiRequest',无法调用formData() - 使用multer存储图片:仍出现相同的
TypeError: s is not a function错误
错误原因及修复说明
- App Router误用Pages Router配置:原代码中的
export const config = { api: { bodyParser: false } }是Pages Router专属配置,App Router默认不解析formData,该配置会导致请求解析异常,需删除。 - 回调版
writeFile与await混用:原代码用await调用回调版fs.writeFile,但该方法不返回Promise,await非Promise值会打断异步流程;同时回调内直接返回NextResponse不符合路由函数的返回逻辑,需替换为fs/promises的Promise版本。 - 路径问题:原代码使用绝对路径
join("/","public/...")会导致跨环境路径错误,改用process.cwd()拼接项目根目录确保路径正确。 - 多余参数:App Router的路由函数不需要
res: NextResponse参数,直接返回NextResponse即可。
内容的提问来源于stack exchange,提问作者Ajaz
相关产品推荐
相关产品推荐

