Download a File From App Router API in Next.js
You need to run server side code that can serve files to make file download work in Next.js. For that you can set up an API endpoint using App Router that can handle requests and respond with file data. If you are using Pages Router, you can check out this post instead.
Creating an API Endpoint in App Router
Let’s set up a basic API with a route that can serve files in Next.js
-
Create a folder called
api
insidesrc/app
directoryIf your project does not use
src
folder, then it should beapp/api
-
Create a folder inside
api
calledfile
You can choose other name than
file
if you wish. The name of this folder will be later used as the URL for your endpoint —/api/file
in this case. -
Create a file
route.ts
inside the file folder
This should be the resulting file structure:
app/
├─ api/
│ ├─ file/
│ │ ├─ route.ts
Now add the following code to stream some data from your API endpoint:
export async function GET() {
const buffer = Buffer.from('hello world', 'utf8');
const headers = new Headers();
headers.append('Content-Disposition', 'attachment; filename="filename.txt"');
headers.append('Content-Type', 'application/text');
return new Response(buffer, {
headers,
});
}
A couple of things to note here:
- Exporting a function called
GET
creates aGET
request handler forapi/file
route of the API - Instead of reading a file from file system, for now you can use a buffer and encode a
hello world
message to send as a plain text file - You need to set the appropriate headers using the
Headers
interfaceappend
method. Adjust theContent-Disposition
header to the filename you wish to use. And adjust theContent-Type
to the file type you wish to send. - In the end, return a
Response
and pass it your file and the headers you have set up
Sending a File from File System
If you wish to send a file from the file system in Next.js, here’s how to adjust the above code for that.
In this example, I created a src/app/assets
folder and placed an image inside of it called image.png
.
import { readFile } from 'fs/promises';
import path from 'path';
export async function GET() {
const buffer = await readFile(path.join(process.cwd(), 'src/app/assets', 'image.png'));
const headers = new Headers();
headers.append('Content-Disposition', 'attachment; filename="image.png"');
headers.append('Content-Type', 'image/png');
return new Response(buffer, {
headers,
});
}
- You can read the file with
readFile
method and pass it the absolute path to your file - An absolute path can be constructed using
path.join
method and putting together the root directory of your project along with the path to the file and the filename itself - Similarly like before, you need to adjust the headers for the filename and file type you wish to send
- The
buffer
in this case contains the raw bytes representing the file and can be sent to the client directly
Receiving and Download Files on the Client
After successfully creating an API endpoint that responds with the file data you want, it’s time to download if on the client.
Here’s a basic setup that calls the API endpoint and receives the file in client side code.
'use client';
export default function DownloadComponent() {
const handleClick = async () => {
const response = await fetch('/api/file');
const blob = await response.blob();
};
return (
<main>
<button
type="button"
onClick={handleClick}
>
Download
</button>
</main>
);
}
This code is calling the api/file
endpoint and converting its response a blob. In Javascript, blobs are used to represent files in a way that makes it easy to process them.
Now that you have access to the file you received from the API inside the blob
variable, you need to create a download URL for it.
You can use createObjectURL
method that expects a blob for that. You also need a way to trigger the download. Here’s how:
export default function Home() {
const handleClick = async () => {
const response = await fetch('/api/file');
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'image.png';
link.click();
window.URL.revokeObjectURL(url);
};
// ✂️
}
- You need to create an anchor element and attach the download URL to the blob representing the file
- You then set up the link to behave like a download link and simulate a click on it by calling
click
method - After you are done, you should release the URL object with
revokeObjectURL
method to free up browser’s resources