Best way to handle File Uploads in NextJS 15 with App Router?
Unanswered
Bombay posted this in #help-forum
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?
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
Server Action
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:
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
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
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
You can take content size from client to ensure it’s not large, and add that limit in presigned url