Skip to content

Download a File From App Router API in Next.js

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

  1. Create a folder called api inside src/app directory

    If your project does not use src folder, then it should be app/api

  2. Create a folder inside api called file

    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.

  3. 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:

  1. Exporting a function called GET creates a GET request handler for api/file route of the API
  2. 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
  3. You need to set the appropriate headers using the Headers interface append method. Adjust the Content-Disposition header to the filename you wish to use. And adjust the Content-Type to the file type you wish to send.
  4. 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