How to upload a file in Next.js 13+ App Directory with No libraries
Uploading a file with Next.js 13 can be done with no third-party libraries letting you move fast and keep bundle size down.
The key is to use the new APIs in Next.js 13 app directory to read out the data from the form upload.
First, let's take a quick look at what a typical client looks like for file upload:
Client Side
'use client'
import { useState } from 'react'
export function UploadForm() {
const [file, setFile] = useState<File>()
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!file) return
try {
const data = new FormData()
data.set('file', file)
const res = await fetch('/api/upload', {
method: 'POST',
body: data
})
// handle the error
if (!res.ok) throw new Error(await res.text())
} catch (e: any) {
// Handle errors here
console.error(e)
}
}
return (
<form onSubmit={onSubmit}>
<input
type="file"
name="file"
onChange={(e) => setFile(e.target.files?.[0])}
/>
<input type="submit" value="Upload" />
</form>
)
}
This code defines a React functional component called UploadForm
. The component renders a form with a file input field and a submit button, allowing users to upload a file to a server. Here are the key parts:
The useState
hook is used to create a state variable file
and its corresponding update function setFile
.
The onSubmit
function is an asynchronous event handler for the form submission. It performs the following actions:
- Prevents the default form submission behavior.
- Checks if a file is selected. If not, it returns early.
- Creates a
FormData
object and appends the selected file to it. - Sends an HTTP POST request to the
/api/upload
endpoint with the file as its body. - If the response is not successful, it throws an error with the response text.
- Catches any errors that occur and logs them to the console.
Note that you do not need to set the header Content-Type
to be multipart/form-data
. This is done automatically when sending FormData
.
The return
statement renders the form with the following elements:
- A file input field, which sets the
file
state variable when a file is selected. - A submit button that triggers the form submission and calls the
onSubmit
function.
When a user selects a file and clicks the "Upload" button, the onSubmit
function sends the file to the server.
This is pretty standard React code. But how do we handle this on the server and do something with the file?
Server API Route
Create the file app/api/upload/route.ts
and add the following code:
import { writeFile } from 'fs/promises'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
const data = await request.formData()
const file: File | null = data.get('file') as unknown as File
if (!file) {
return NextResponse.json({ success: false })
}
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
// With the file data in the buffer, you can do whatever you want with it.
// For this, we'll just write it to the filesystem in a new location
const path = `/tmp/${file.name}`
await writeFile(path, buffer)
console.log(`open ${path} to see the uploaded file`)
return NextResponse.json({ success: true })
}
This code defines an asynchronous function POST
that handles an HTTP POST request containing a file, reads the file, and then writes it to the server's local filesystem. Here are the key parts:
- The
POST
function takes arequest
parameter of typeNextRequest
. It starts by reading the form data from the request using theawait request.formData()
method. - Extract the uploaded
File
object from the form data usingdata.get('file')
. If no file is found, it returns a JSON response with{ success: false }
. - Read the file content as an ArrayBuffer using
await file.arrayBuffer()
and then convert it into a Node.jsBuffer
object usingBuffer.from(bytes)
. This is key. It turns the data from a Web API object into a Node.js Buffer, allowing you to handle the data easily. - For this demo, with the file data in the
buffer
, the code writes the file to a new location in the server's local filesystem using thewriteFile
function. The new file path is set to/tmp/${file.name}
. - If everything is successful, return a JSON response with
{ success: true }
.
When a client sends a file via an HTTP POST request to this API route, this code reads the file, saves it to the server's filesystem, and returns a JSON response indicating success or failure.
Code
You can grab the full sample project here.