Next.js Discord

Discord Forum

Best way to handle File Uploads in NextJS 15 with App Router?

Unanswered
Bombay posted this in #help-forum
Open in Discord
BombayOP
Hi, I am new to NextJS, coming from RRV7 (Remix) and was wondering what the best way to handle file uploads was?

I want to be able to handle large files, concurrently, with status indicators, etc. Should I use a server action or an API route?

11 Replies

BombayOP
Current approach:

Client-Side Upload Component
"use client";

import Form from "next/form";
import { Button } from "@/components/ui/button";
import { upload } from "@/app/actions/upload";
import { useState } from "react";
import { Card, CardContent, CardTitle } from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Input } from "@/components/ui/input";

export const FileForm = () => {
  const [errorMessage, setErrorMessage] = useState<string | null>(null);

  const handleUpload = async (formData: FormData) => {
    const result = await upload(formData);
    if (!result.success) {
      setErrorMessage(result.message);
    } else {
      setErrorMessage(null);
    }
  };

  return (
    <Card>
      <CardContent className={"flex flex-col p-4 gap-4"}>
        <CardTitle>Upload a File</CardTitle>
        <Form action={handleUpload} className={"flex flex-col gap-4"}>
          <Input type={"file"} name={"file"} multiple />
          <Button type={"submit"}>Upload</Button>
          {errorMessage && (
            <Alert>
              <AlertDescription>{errorMessage}</AlertDescription>
            </Alert>
          )}
        </Form>
      </CardContent>
    </Card>
  );
};


Server Action
"use server";

type UploadResult = {
  success: boolean;
  message: string;
  fileName?: string;
};

export const upload = async (formData: FormData): Promise<UploadResult> => {
  const files = formData.getAll("file");

  if (!files) {
    throw new Error("No file uploaded");
  }

  console.log(files);

  return { success: true, message: "File uploaded successfully" };
};
You could plug in useActionState to have access to a isLoading flag returned by the hook:

const [state, action, isLoading] = useActionState(yourServerAction, initialState);


Where:
- state is whatever you return from the Server Action, will be typesafe (here you might return {message: “success message”, error: “error message} or whatever you want)

- action will be the function you’ll pass to the <form action> and

- isLoading is true while the action is running inside a transition in the background
BombayOP
the problem with the server action approach is limited file upload size
im talking like 5gb files and bigger
@Bombay Current approach: Client-Side Upload Component ts "use client"; import Form from "next/form"; import { Button } from "@/components/ui/button"; import { upload } from "@/app/actions/upload"; import { useState } from "react"; import { Card, CardContent, CardTitle } from "@/components/ui/card"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Input } from "@/components/ui/input"; export const FileForm = () => { const [errorMessage, setErrorMessage] = useState<string | null>(null); const handleUpload = async (formData: FormData) => { const result = await upload(formData); if (!result.success) { setErrorMessage(result.message); } else { setErrorMessage(null); } }; return ( <Card> <CardContent className={"flex flex-col p-4 gap-4"}> <CardTitle>Upload a File</CardTitle> <Form action={handleUpload} className={"flex flex-col gap-4"}> <Input type={"file"} name={"file"} multiple /> <Button type={"submit"}>Upload</Button> {errorMessage && ( <Alert> <AlertDescription>{errorMessage}</AlertDescription> </Alert> )} </Form> </CardContent> </Card> ); }; Server Action ts "use server"; type UploadResult = { success: boolean; message: string; fileName?: string; }; export const upload = async (formData: FormData): Promise<UploadResult> => { const files = formData.getAll("file"); if (!files) { throw new Error("No file uploaded"); } console.log(files); return { success: true, message: "File uploaded successfully" }; };
Oh I thought your current approach worked and I was suggesting some improvements.
BombayOP
streaming with busboy possibly for big files?
if you're uploading to a S3 compatible storage solution, use presigned URLs
you send a request to backend and it gives you a url, you then send that file to that url instead of your backend
Northeast Congo Lion
The solution of @Yi Lon Ma is the one I use, it makes it easy and works for any file size
You can use Cloudinary too or any other file storage provider
@Yi Lon Ma you send a request to backend and it gives you a url, you then send that file to that url instead of your backend
+1 that’s what I’ve done.

You can take content size from client to ensure it’s not large, and add that limit in presigned url