在 Next.js 中轻松实现文件上传

July 5, 2024

Next.js React

通过两种简单的方法在 Next.js 中轻松上传文件:Server Action 或自定义 API Route

cover

在当今的 Web 开发中,文件上传是一个常见且重要的功能。Next.js 为我们提供了多种便捷的方式来实现文件上传,本文将详细介绍其中两种常用的方法。

使用 Server Action 上传文件

Server Action 是 Next.js 中处理文件上传的一种简单而直接的方式。 首先,假设我们有一个简单的图像上传应用程序,它需要获取用户上传的图像并将它们存储在 /public/uploads 目录中。为此,我们创建一个接收上传文件的 Server Action 函数:

"use server";
import fs from "node:fs/promises";
import { revalidatePath } from "next/cache";

export async function uploadFile(formData: FormData) {
  const file = formData.get("file") as File;
  const arrayBuffer = await file.arrayBuffer();
  const buffer = new Uint8Array(arrayBuffer);

  await fs.writeFile(`./public/uploads/${file.name}`, buffer);

  revalidatePath("/");
}

这里的 uploadFile 函数从 FormData 对象中获取文件,并将其写入指定目录。需要注意的是,在实际项目中,我们通常不会将文件直接写入本地的 /public/uploads 目录,而是会选择像 S3 这样的云存储服务。此处只是为了提供一个简单的示例,所以采用了本地文件系统。 接下来,我们创建一个用于上传文件的表单页面:

import Image from "next/image";
import fs from "node:fs/promises";

import UploadForm from "./UploadForm";

export default async function Home() {
  const files = await fs.readdir("./public/uploads");
  const images = files.filter((file) => file.endsWith(".jpg")).map((file) => `/uploads/${file}`);

  return (
    <main>
      <UploadForm />
      <div className="flex flex-wrap">
        {images.map((image) => (
          <div key={image} className="px-2 h-auto w-1/2">
            <Image key={image} src={image} width={400} height={400} alt={image} className="object-cover w-full" />
          </div>
        ))}
      </div>
    </main>
  );
}

这是一个 React Server 组件,它能够使用 fs 读取 /public/uploads 目录中的文件,并筛选出图像文件进行展示。同样,这里为了简化,我们使用了本地文件系统,您在实际开发中可以将 fs 调用替换为对云存储服务的调用,或者从与用户关联的数据库中获取图像。 然后,我们还需要创建 UploadForm 组件:

该页面还引入了我们接下来要创建的 UploadForm 组件。

"use client";
import { uploadFile } from "./upload-action";

export default function UploadForm() {
  return (
    <form action={uploadFile} className="flex flex-col gap-4">
      <label>
        <span>Upload a file</span>
        <input type="file" name="file" ref={fileInput} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

这是一个简单的表单,包含文件输入和提交按钮。action 属性设置为我们之前定义的 uploadFile 函数,这是 Next.js 的一项特色功能,允许表单直接调用服务器操作。 当您点击“提交”按钮时,文件上传操作就会被触发。如果您查看开发人员控制台,会发现 Next.js 在客户端自动生成 POST 请求,并将其路由到 Server Action 进行处理。

使用 API Route

除了 Server Action,使用 API Route 也是在 Web 应用程序中处理文件上传的常见方法。首先,在 /api/uploadImage 目录下创建 route.ts 文件:

import { NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
import fs from "node:fs/promises";

export async function POST(req: Request) {
  try {
    const formData = await req.formData();

    const file = formData.get("file") as File;
    const arrayBuffer = await file.arrayBuffer();
    const buffer = new Uint8Array(arrayBuffer);
    await fs.writeFile(`./public/uploads/${file.name}`, buffer);

    revalidatePath("/");

    return NextResponse.json({ status: "success" });
  } catch (e) {
    console.error(e);
    return NextResponse.json({ status: "fail", error: e });
  }
}

这段代码的主体部分与 Server Action 中的类似,只是在获取请求中的 formData 时有所不同。并且,我们还返回了一个 JSON 响应。 相应地, UploadForm 的代码也会有所变化:

"use client";
import { useRef } from "react";

export default function UploadForm() {
  const fileInput = useRef<HTMLInputElement>(null);

  async function uploadFile(
    evt: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) {
    evt.preventDefault();

    const formData = new FormData();
    formData.append("file", fileInput?.current?.files?.[0]!);

    const response = await fetch("/api/uploadImage", {
      method: "POST",
      body: formData,
    });
    const result = await response.json();
    console.log(result);
  }

  return (
    <form className="flex flex-col gap-4">
      <label>
        <span>Upload a file</span>
        <input type="file" name="file" ref={fileInput} />
      </label>
      <button type="submit" onClick={uploadFile}>
        Submit
      </button>
    </form>
  );
}

在这种方式下,我们不再通过 action 属性将表单提交给服务器,而是在提交按钮的 onClick 事件中调用 uploadFile 函数,通过 fetch 发送文件到 API Route 进行处理。

总结

Next.js 的 Server Action 为文件上传提供了简洁高效的解决方案。当您希望更直接、简单地处理文件上传时,它是一个不错的选择。然而,如果您需要对文件上传的机制有更多的控制权和定制化需求,使用 API Route 自行管理上传过程则更加灵活。