Downloading Files in Next.js Using Pages Router
To download files in Next.js you will need to stream them from the server.
In this tutorial, you will set up an API route using Pages Router to handle server side code. If you use App Router for creating client side pages, you can still use Pages Router for the API. You can also check out this post where I show how to do file download from API using App Router.
Creating an API Route Handler
To create an API route that handles requests that will download your file, you need to create a specific folder structure.
-
Create a folder called
api
insidepages
folder -
Create a folder with a name for the route that will handle the request
For example, if you want the API route to be
/api/file
you should make a folderpages/api/file
-
Create a file called
index.ts
insidefile
folder
Alternatively, you can make a pages/api/file.ts
file and handle the API route there.
Now, export a default function from your newly created file to create a route handler.
import { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
request: NextApiRequest,
response: NextApiResponse
) {
response.json({ message: 'Response message' });
}
You should receive a JSON response when sending any kind of request to /api/file
.
To limit what HTTP methods your endpoint accepts, you can check if request.method
matches one that you don’t want.
export default async function handler(
request: NextApiRequest,
response: NextApiResponse
) {
if (request.method !== 'GET') {
return response.status(405).end();
}
response.json({ message: 'Response message' });
}
You can change GET
to any other method you wish to use. Sending a response with 405 status code indicates that a method is not allowed.
Streaming a File from API Route
First things first, you will need a file that you want to download. You can create a src/assets
folder and place it there. For example, create a src/assets/file.txt
and write hello
inside of it.
To download the file, add the following code to the route handler function:
import { NextApiRequest, NextApiResponse } from 'next';
import fs from 'fs';
import * as path from 'path';
export default async function handler(request: NextApiRequest, response: NextApiResponse) {
if (request.method !== 'GET') {
return response.status(405).end();
}
response.setHeader('Content-Disposition', 'attachment; filename=filename.txt');
response.setHeader('Content-Type', 'application/text');
const filePath = path.join(process.cwd(), 'src/assets', 'file.txt');
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(response);
response.on('finish', () => {
fileStream.close();
});
}
A couple of things to note here:
- Depending on what kind of file you are sending, you can specify its file type in
Content-Type
header - Similarly, you can adjust
Content-Disposition
header to specify a filename, but it won’t affect the downloaded file name later - You can use
process.cwd()
together withpath.join
method to build the absolute path to your desired file
It is recommended to call the close
method to close the stream after the request has been handled and the finish
event is emitted.
Downloading the File
Now that your API handler is streaming a file as a response, all that’s left is to actually download it onto your computer.
Make a request to the API route and read the response stream using blob
method.
import React from 'react';
export default function Home() {
const handleClick = async () => {
const response = await fetch('/api/file');
if (response.status !== 200) {
console.error(response.status, response.statusText);
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'filename.txt';
link.click();
};
return (
<button
type="button"
onClick={handleClick}
>
Download
</button>
);
}
Now that you have receive a Blob from your API, you can create a URL for it and attach it to an anchor element.
You don’t actually need to place the created anchor element into the page itself, you can just simulate a click on it to begin download.
Summary
In short, to download a file from a Next.js API route, you need to do the following:
- Create an API route handler inside
pages/api
- Stream the file to your client
- In the client, fetch the API endpoint and read the response stream as a Blob
- Create a URL for the Blob and attach it to an anchor element
- Simulate a click onto the anchor element to begin download