Next.js API routes: How to read files from directory (compatible with vercel)

Lately I was having a use case of reading images from an in-project-directory in Next.js. I couldn’t use getStaticProps (because it was conflicting with getInitialProps, which I had to use for another library), so I had to use an api route in order to read the images (or rather their paths) from the directory.

The images are lying in /public/img and its amount is variable, which is why we want to dynamically read (and fetch) all the images in there (not manually adapt our code every time a new image is added):

A screenshot of the folder structure for our images
A screenshot of the folder structure for our images
The folder structure of our images, that should be dynamically read

Page Code

Let’s first intialize our code for our page:

// pages/index.js
import useSWR from 'swr';

export default function Home() {
const fetcher = (url) => fetch(url).then((res) => res.json());
const { data } = useSWR('/api/readfiles', fetcher);

return (
<div>
...

<main>
<h1>Images read from API route: </h1>
{!data && "Loading..."}
{data && data.map(imgPath => <img src={imgPath} alt=""/>)}
</main>

...
</div>
)
}

Okay, so we want to use swr in order to fetch the images from our api route. As soon as the data is ready, we want to map over the returned data. The data is expected to be an array of image paths.

API Route: The wrong approach

The approach, as proposed here, works to display the images correctly:

// pages/api/readfiles.js
import fs from 'fs'
import path from 'path'
import getConfig from 'next/config'

export default (req, res) => {
const { serverRuntimeConfig } = getConfig()

const dirRelativeToPublicFolder = 'img'

const dir = path.join(serverRuntimeConfig.PROJECT_ROOT, './public', dirRelativeToPublicFolder);

const filenames = fs.readdirSync(dir);

const images = filenames.map(name => path.join('/', dirRelativeToPublicFolder, name))

res.statusCode = 200
res.json(images);
}
// next.config.js
module.exports = {
serverRuntimeConfig: {
PROJECT_ROOT: __dirname
}
}

As we can see when running npm run dev, the images are displayed in the browser:

But after deploying our project to vercel, the following is shown:

Image for post
Image for post
On vercel, the images are loading infinitely

When inspecting the logs on the vercel dashboard, it gets clear, that our images folder is not found (because it’s not included in the serverless lambda):

ERROR: ENOENT: No such file or directory, scandir '/vercel/workpath0/public/img'

So let’s refactor our api route to be compatible with vercel!

API Route: The right approach

First of all, we can delete our next.config.js or at least remove the PROJECT_ROOT entry.

Lastly, let’s update our API route to have the following code:

import fs from 'fs'
import path from 'path'

export default (req, res) => {
const dirRelativeToPublicFolder = 'img'

const dir = path.resolve('./public', dirRelativeToPublicFolder);

const filenames = fs.readdirSync(dir);

const images = filenames.map(name => path.join('/', dirRelativeToPublicFolder, name))

res.statusCode = 200
res.json(images);
}

The important part is path.resolve(...), which instructs vercel to include the scanned path in the serverless lambda. The shown code works to read images (or other files from your fs) both locally and remotely on vercel!

Conclusion

Use the above code snippet to read files in your api route in a way that vercel doesn’t complain.

If you want to take a look at the complete project, check out the GitHub repository.

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store