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

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错误

错误原因及修复说明

  1. App Router误用Pages Router配置:原代码中的export const config = { api: { bodyParser: false } }是Pages Router专属配置,App Router默认不解析formData,该配置会导致请求解析异常,需删除。
  2. 回调版writeFile与await混用:原代码用await调用回调版fs.writeFile,但该方法不返回Promise,await非Promise值会打断异步流程;同时回调内直接返回NextResponse不符合路由函数的返回逻辑,需替换为fs/promises的Promise版本。
  3. 路径问题:原代码使用绝对路径join("/","public/...")会导致跨环境路径错误,改用process.cwd()拼接项目根目录确保路径正确。
  4. 多余参数:App Router的路由函数不需要res: NextResponse参数,直接返回NextResponse即可。

内容的提问来源于stack exchange,提问作者Ajaz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 20:44:55