Skip to content

Downloading Files in Next.js Using Pages Router

Next.js/

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.

  1. Create a folder called api inside pages folder

  2. 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 folder pages/api/file

  3. Create a file called index.ts inside file 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:

  1. Depending on what kind of file you are sending, you can specify its file type in Content-Type header
  2. Similarly, you can adjust Content-Disposition header to specify a filename, but it won’t affect the downloaded file name later
  3. You can use process.cwd() together with path.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:

  1. Create an API route handler inside pages/api
  2. Stream the file to your client
  3. In the client, fetch the API endpoint and read the response stream as a Blob
  4. Create a URL for the Blob and attach it to an anchor element
  5. Simulate a click onto the anchor element to begin download